diff --git a/.gitignore b/.gitignore
index 5581c38e..4718e79b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,9 @@
/release.properties
/demo/target
/.idea
+/android-demo/build
+/android-demo/.gradle
+/android-demo/local.properties
+/android-demo/gradlew
+/android-demo/gradle
+/android-demo/gradlew.bat
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..7581c084
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,9 @@
+language: java
+sudo: false # faster builds
+
+install: true
+
+script: "mvn test && mvn cobertura:cobertura"
+
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..c93b3bdd
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,68 @@
+# 0.9.22
+
+* fix #167 parse Object.class follow jackson convention. fixed the case of 1.0 parsed as int not double.
+* fix #154 support map integer key
+* fix #152
+
+# 0.9.21
+
+breaking changes
+
+* fix #149 parse Object.class follow jackson convention
+
+bug fixes
+
+* fix #145 add Any.registerEncoders
+* merge #143
+
+# 0.9.20
+
+* fix #136, field with only getter is also considered as java bean property, so that @JsonIgnore on the field should be propagated to getter
+
+# 0.9.19
+* changed cfg class name to hashcode based
+* fix static codegen
+* fix #133 NPE when no extra
+* fix #132 MaybeEmptyArrayDecoder
+* fix #130 @JsonCreator not compatible with @JsonIgnore
+* fix #126 surrogate unicode
+
+# 0.9.18
+* fix of overflow detection for numeric primitive types
+* fix of method prefix of error message
+* issue #125 avoid nested JsonException
+* fix #109 treat wildcard generics variable as Object
+
+# 0.9.17
+* fix leading zero
+* fix #112 #119
+* fix of parsing zero & min values
+* issue #115 better leading zero detection
+* fix #144, parse max int/long
+* fix #110 if @JsonProperty is marked on field, ignore getter/setter
+
+# 0.9.16
+
+* issue #107 annotation should be marked on getter/setter if present
+* fix ctor is null when encoding issue
+* issue #104, JsonWrapper argument should not be mandatory
+* issue #99 added mustBeValid method to Any class
+* issue #97 demonstrate JsonProperty when both field and setter
+* like "1.0e+10" should not fail
+* issue #94 skip transient field
+* issue #94 fix JsonProperty not changing fromNames and toNames
+* issue #93 some control character should be esacped specially
+* issue #93 fix control character serialization
+* issue #92 fix generics support
+
+# 0.9.15
+
+breaking changes
+
+* `null` is not omitted by default config
+
+new features
+
+* add `defaultValueToOmit` to @JsonProperty
+* add `omitDefaultValue` to config
+* encoder support indention in dynamic mode
\ No newline at end of file
diff --git a/README.md b/README.md
index a3b95356..b621a36c 100644
--- a/README.md
+++ b/README.md
@@ -1,69 +1,8 @@
-jsoniter (json-iterator) is fast and flexible JSON parser available in [Java](https://github.com/json-iterator/java) and [Go](https://github.com/json-iterator/go)
+[](https://travis-ci.org/json-iterator/java)
+[](https://codecov.io/gh/json-iterator/java)
+[](https://raw.githubusercontent.com/json-iterator/java/master/LICENSE)
+[](https://gitter.im/json-iterator/Lobby)
-# Why jsoniter?
+Documentation : [http://jsoniter.com/java-features.html](http://jsoniter.com/java-features.html)
-* Jsoniter is the fastest JSON parser. It could be up to 10x faster than normal parser, data binding included. Shameless self [benchmark](http://jsoniter.com/benchmark.html)
-* Extremely flexible api. You can mix and match three different styles: bind-api, any-api or iterator-api. Checkout your [api choices](http://jsoniter.com/api.html)
-
-# Show off
-
-Here is a quick show off, for more complete report you can checkout the full [benchmark](http://jsoniter.com/benchmark.html) with [in-depth optimization](http://jsoniter.com/benchmark.html#optimization-used) to back the numbers up
-
-
-
-# Bind-API is the best
-
-Bind-api should always be the first choice. Given this JSON document `[0,1,2,3]`
-
-Parse with Java bind-api
-
-```java
-import com.jsoniter.JsonIterator;
-JsonIterator iter = JsonIterator.parse("[0,1,2,3]");
-int[] val = iter.read(int[].class);
-System.out.println(val[3]);
-```
-
-# Iterator-API for quick extraction
-
-When you do not need to get all the data back, just extract some.
-
-Parse with Java iterator-api
-
-```java
-import com.jsoniter.JsonIterator;
-JsonIterator iter = JsonIterator.parse("[0, [1, 2], [3, 4], 5]");
-int count = 0;
-while(iter.readArray()) {
- iter.skip();
- count++;
-}
-System.out.println(count); // 4
-```
-
-# Any-API for maximum flexibility
-
-Parse with Java any-api
-
-```java
-import com.jsoniter.JsonIterator;
-JsonIterator iter = JsonIterator.parse("[{'field1':'11','field2':'12'},{'field1':'21','field2':'22'}]".replace('\'', '"'));
-Any val = iter.readAny();
-System.out.println(val.toInt(1, "field2")); // 22
-```
-
-Notice you can extract from nested data structure, and convert any type to the type to you want.
-
-# How to get
-
-```
-
- com.jsoniter
- jsoniter
- 0.9.4
-
-```
-
-# Contribution Welcomed !
-
-Report issue or pull request, or email taowen@gmail.com, or [](https://gitter.im/json-iterator/Lobby)
+Scala User: https://github.com/plokhotnyuk/jsoniter-scala
\ No newline at end of file
diff --git a/android-demo/.gitignore b/android-demo/.gitignore
new file mode 100644
index 00000000..a94ca08d
--- /dev/null
+++ b/android-demo/.gitignore
@@ -0,0 +1,3 @@
+/build
+/.idea
+/gradle.properties
diff --git a/android-demo/build.gradle b/android-demo/build.gradle
new file mode 100644
index 00000000..ed3538b4
--- /dev/null
+++ b/android-demo/build.gradle
@@ -0,0 +1,71 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 27
+ buildToolsVersion "27.0.2"
+
+ defaultConfig {
+ applicationId "com.example.myapplication"
+ minSdkVersion 15
+ targetSdkVersion 27
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ compile 'com.android.support:appcompat-v7:27.+'
+ compile 'com.android.support.constraint:constraint-layout:+'
+ compile 'com.jsoniter:jsoniter:0.9.19-SNAPSHOT'
+ testCompile 'junit:junit:4.12'
+}
+
+buildscript {
+ repositories {
+ maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
+ google()
+ mavenLocal()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.0' //last version Jan 2016
+ }
+}
+
+allprojects {
+ repositories {
+ maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
+ google()
+ mavenLocal()
+ }
+}
+
+afterEvaluate {
+ android.applicationVariants.all { variant ->
+ variant.javaCompiler.finalizedBy(jsoniterStaticCodgen)
+ }
+}
+
+task jsoniterStaticCodgen(type:JavaExec) {
+ classpath configurations.getByName(android.sourceSets.main.compileConfigurationName)
+ classpath project.buildDir.toString() + '/intermediates/classes/release'
+ classpath project.buildDir.toString() + '/intermediates/classes/debug'
+ main = 'com.jsoniter.static_codegen.StaticCodegen'
+ args 'com.example.myapplication.DemoCodegenConfig'
+ workingDir = android.sourceSets.main.java.srcDirs[0].toString()
+ standardOutput = System.out
+ errorOutput = System.err
+}
+
diff --git a/android-demo/proguard-rules.pro b/android-demo/proguard-rules.pro
new file mode 100644
index 00000000..35c57bef
--- /dev/null
+++ b/android-demo/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/xiaoju/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/android-demo/settings.gradle b/android-demo/settings.gradle
new file mode 100644
index 00000000..b63b000f
--- /dev/null
+++ b/android-demo/settings.gradle
@@ -0,0 +1 @@
+include ':android-demo'
diff --git a/android-demo/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.java b/android-demo/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..24ffb078
--- /dev/null
+++ b/android-demo/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.myapplication;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.example.myapplication", appContext.getPackageName());
+ }
+}
diff --git a/android-demo/src/main/AndroidManifest.xml b/android-demo/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..c9d1f59f
--- /dev/null
+++ b/android-demo/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android-demo/src/main/java/com/example/myapplication/DemoCodegenConfig.java b/android-demo/src/main/java/com/example/myapplication/DemoCodegenConfig.java
new file mode 100644
index 00000000..53e756d1
--- /dev/null
+++ b/android-demo/src/main/java/com/example/myapplication/DemoCodegenConfig.java
@@ -0,0 +1,49 @@
+package com.example.myapplication;
+
+import com.jsoniter.JsonIterator;
+import com.jsoniter.output.EncodingMode;
+import com.jsoniter.output.JsonStream;
+import com.jsoniter.spi.Decoder;
+import com.jsoniter.spi.DecodingMode;
+import com.jsoniter.spi.JsoniterSpi;
+import com.jsoniter.spi.TypeLiteral;
+import com.jsoniter.static_codegen.StaticCodegenConfig;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public class DemoCodegenConfig implements StaticCodegenConfig {
+
+ @Override
+ public void setup() {
+ // register custom decoder or extensions before codegen
+ // so that we doing codegen, we know in which case, we need to callback
+ JsonIterator.setMode(DecodingMode.STATIC_MODE);
+ JsonStream.setMode(EncodingMode.STATIC_MODE);
+ JsonStream.setIndentionStep(2);
+ JsoniterSpi.registerPropertyDecoder(User.class, "score", new Decoder.IntDecoder() {
+ @Override
+ public int decodeInt(JsonIterator iter) throws IOException {
+ return Integer.valueOf(iter.readString());
+ }
+ });
+ }
+
+ @Override
+ public TypeLiteral[] whatToCodegen() {
+ return new TypeLiteral[]{
+ // generic types, need to use this syntax
+ new TypeLiteral>() {
+ },
+ new TypeLiteral>() {
+ },
+ new TypeLiteral>() {
+ },
+ // array
+ TypeLiteral.create(int[].class),
+ // object
+ TypeLiteral.create(User.class)
+ };
+ }
+}
diff --git a/android-demo/src/main/java/com/example/myapplication/MainActivity.java b/android-demo/src/main/java/com/example/myapplication/MainActivity.java
new file mode 100644
index 00000000..c9d8fa7e
--- /dev/null
+++ b/android-demo/src/main/java/com/example/myapplication/MainActivity.java
@@ -0,0 +1,17 @@
+package com.example.myapplication;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+import com.jsoniter.JsonIterator;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ User user = JsonIterator.deserialize("{\"firstName\": \"tao\", \"lastName\": \"wen\", \"score\": 1024}", User.class);
+ Log.d("jsoniter", user.firstName);
+ }
+}
diff --git a/android-demo/src/main/java/com/example/myapplication/User.java b/android-demo/src/main/java/com/example/myapplication/User.java
new file mode 100644
index 00000000..e7fd317c
--- /dev/null
+++ b/android-demo/src/main/java/com/example/myapplication/User.java
@@ -0,0 +1,11 @@
+package com.example.myapplication;
+
+import com.jsoniter.annotation.JsonProperty;
+
+public class User {
+ @JsonProperty(nullable = false)
+ public String firstName;
+ @JsonProperty(nullable = false)
+ public String lastName;
+ public int score;
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/com/example/myapplication/User.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/com/example/myapplication/User.java
new file mode 100644
index 00000000..baef2fa8
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/com/example/myapplication/User.java
@@ -0,0 +1,55 @@
+package jsoniter_codegen.cfg1173796797.decoder.com.example.myapplication;
+public class User implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { java.lang.Object existingObj = com.jsoniter.CodegenAccess.resetExistingObject(iter);
+byte nextToken = com.jsoniter.CodegenAccess.readByte(iter);
+if (nextToken != '{') {
+if (nextToken == 'n') {
+com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);
+return null;
+} else {
+nextToken = com.jsoniter.CodegenAccess.nextToken(iter);
+if (nextToken == 'n') {
+com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);
+return null;
+}
+} // end of if null
+} // end of if {
+nextToken = com.jsoniter.CodegenAccess.readByte(iter);
+if (nextToken != '"') {
+if (nextToken == '}') {
+return (existingObj == null ? new com.example.myapplication.User() : (com.example.myapplication.User)existingObj);
+} else {
+nextToken = com.jsoniter.CodegenAccess.nextToken(iter);
+if (nextToken == '}') {
+return (existingObj == null ? new com.example.myapplication.User() : (com.example.myapplication.User)existingObj);
+} else {
+com.jsoniter.CodegenAccess.unreadByte(iter);
+}
+} // end of if end
+} else { com.jsoniter.CodegenAccess.unreadByte(iter); }// end of if not quote
+java.lang.String _firstName_ = null;
+java.lang.String _lastName_ = null;
+int _score_ = 0;
+do {
+switch (com.jsoniter.CodegenAccess.readObjectFieldAsHash(iter)) {
+case -1078100014:
+_lastName_ = (java.lang.String)iter.readString();
+continue;
+case -799547430:
+_firstName_ = (java.lang.String)iter.readString();
+continue;
+case -768634731:
+_score_ = (int)com.jsoniter.CodegenAccess.readInt("score@jsoniter_codegen.cfg1173796797.decoder.com.example.myapplication.User", iter);
+continue;
+}
+iter.skip();
+} while (com.jsoniter.CodegenAccess.nextTokenIsComma(iter));
+com.example.myapplication.User obj = (existingObj == null ? new com.example.myapplication.User() : (com.example.myapplication.User)existingObj);
+obj.firstName = _firstName_;
+obj.lastName = _lastName_;
+obj.score = _score_;
+return obj;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/int_array.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/int_array.java
new file mode 100644
index 00000000..9ff993e7
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/int_array.java
@@ -0,0 +1,60 @@
+package jsoniter_codegen.cfg1173796797.decoder;
+public class int_array implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { com.jsoniter.CodegenAccess.resetExistingObject(iter);
+byte nextToken = com.jsoniter.CodegenAccess.readByte(iter);
+if (nextToken != '[') {
+if (nextToken == 'n') {
+com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);
+com.jsoniter.CodegenAccess.resetExistingObject(iter); return null;
+} else {
+nextToken = com.jsoniter.CodegenAccess.nextToken(iter);
+if (nextToken == 'n') {
+com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);
+com.jsoniter.CodegenAccess.resetExistingObject(iter); return null;
+}
+}
+}
+nextToken = com.jsoniter.CodegenAccess.nextToken(iter);
+if (nextToken == ']') {
+return new int[0];
+}
+com.jsoniter.CodegenAccess.unreadByte(iter);
+int a1 = (int)iter.readInt();
+if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+return new int[]{ a1 };
+}
+int a2 = (int)iter.readInt();
+if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+return new int[]{ a1, a2 };
+}
+int a3 = (int)iter.readInt();
+if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+return new int[]{ a1, a2, a3 };
+}
+int a4 = (int) (int)iter.readInt();
+if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+return new int[]{ a1, a2, a3, a4 };
+}
+int a5 = (int) (int)iter.readInt();
+int[] arr = new int[10];
+arr[0] = a1;
+arr[1] = a2;
+arr[2] = a3;
+arr[3] = a4;
+arr[4] = a5;
+int i = 5;
+while (com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+if (i == arr.length) {
+int[] newArr = new int[arr.length * 2];
+System.arraycopy(arr, 0, newArr, 0, arr.length);
+arr = newArr;
+}
+arr[i++] = (int)iter.readInt();
+}
+int[] result = new int[i];
+System.arraycopy(arr, 0, result, 0, i);
+return result;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_com/example/myapplication/User.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_com/example/myapplication/User.java
new file mode 100644
index 00000000..41966727
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_com/example/myapplication/User.java
@@ -0,0 +1,42 @@
+package jsoniter_codegen.cfg1173796797.decoder.java.util.List_com.example.myapplication;
+public class User implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { java.util.ArrayList col = (java.util.ArrayList)com.jsoniter.CodegenAccess.resetExistingObject(iter);
+if (iter.readNull()) { com.jsoniter.CodegenAccess.resetExistingObject(iter); return null; }
+if (!com.jsoniter.CodegenAccess.readArrayStart(iter)) {
+return col == null ? new java.util.ArrayList(0): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+}
+Object a1 = (com.example.myapplication.User)jsoniter_codegen.cfg1173796797.decoder.com.example.myapplication.User.decode_(iter);
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(1): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+return obj;
+}
+Object a2 = (com.example.myapplication.User)jsoniter_codegen.cfg1173796797.decoder.com.example.myapplication.User.decode_(iter);
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(2): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+return obj;
+}
+Object a3 = (com.example.myapplication.User)jsoniter_codegen.cfg1173796797.decoder.com.example.myapplication.User.decode_(iter);
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(3): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+obj.add(a3);
+return obj;
+}
+Object a4 = (com.example.myapplication.User)jsoniter_codegen.cfg1173796797.decoder.com.example.myapplication.User.decode_(iter);
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(8): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+obj.add(a3);
+obj.add(a4);
+while (com.jsoniter.CodegenAccess.nextToken(iter) == ',') {
+obj.add((com.example.myapplication.User)jsoniter_codegen.cfg1173796797.decoder.com.example.myapplication.User.decode_(iter));
+}
+return obj;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_java/lang/Integer.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_java/lang/Integer.java
new file mode 100644
index 00000000..419b2413
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_java/lang/Integer.java
@@ -0,0 +1,42 @@
+package jsoniter_codegen.cfg1173796797.decoder.java.util.List_java.lang;
+public class Integer implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { java.util.ArrayList col = (java.util.ArrayList)com.jsoniter.CodegenAccess.resetExistingObject(iter);
+if (iter.readNull()) { com.jsoniter.CodegenAccess.resetExistingObject(iter); return null; }
+if (!com.jsoniter.CodegenAccess.readArrayStart(iter)) {
+return col == null ? new java.util.ArrayList(0): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+}
+Object a1 = (java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt()));
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(1): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+return obj;
+}
+Object a2 = (java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt()));
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(2): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+return obj;
+}
+Object a3 = (java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt()));
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(3): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+obj.add(a3);
+return obj;
+}
+Object a4 = (java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt()));
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(8): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+obj.add(a3);
+obj.add(a4);
+while (com.jsoniter.CodegenAccess.nextToken(iter) == ',') {
+obj.add((java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt())));
+}
+return obj;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/Map_java/lang/String_java/lang/Object.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/Map_java/lang/String_java/lang/Object.java
new file mode 100644
index 00000000..3250faa4
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/Map_java/lang/String_java/lang/Object.java
@@ -0,0 +1,17 @@
+package jsoniter_codegen.cfg1173796797.decoder.java.util.Map_java.lang.String_java.lang;
+public class Object implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { java.util.HashMap map = (java.util.HashMap)com.jsoniter.CodegenAccess.resetExistingObject(iter);
+if (iter.readNull()) { return null; }
+if (map == null) { map = new java.util.HashMap(); }
+if (!com.jsoniter.CodegenAccess.readObjectStart(iter)) {
+return map;
+}
+do {
+java.lang.Object mapKey = com.jsoniter.CodegenAccess.readObjectFieldAsString(iter);
+map.put(mapKey, (java.lang.Object)iter.read());
+} while (com.jsoniter.CodegenAccess.nextToken(iter) == ',');
+return map;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/com/example/myapplication/User.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/com/example/myapplication/User.java
new file mode 100644
index 00000000..03e714c6
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/com/example/myapplication/User.java
@@ -0,0 +1,20 @@
+package jsoniter_codegen.cfg1173796797.encoder.com.example.myapplication;
+public class User implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((com.example.myapplication.User)obj, stream);
+}
+public static void encode_(com.example.myapplication.User obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+stream.writeObjectStart();
+stream.writeIndention();
+stream.writeObjectField("firstName");
+stream.writeVal((java.lang.String)obj.firstName);
+stream.writeMore();
+stream.writeObjectField("lastName");
+stream.writeVal((java.lang.String)obj.lastName);
+stream.writeMore();
+stream.writeObjectField("score");
+stream.writeVal((int)obj.score);
+stream.writeObjectEnd();
+}
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/int_array.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/int_array.java
new file mode 100644
index 00000000..e799b768
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/int_array.java
@@ -0,0 +1,21 @@
+package jsoniter_codegen.cfg1173796797.encoder;
+public class int_array implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((int[])obj, stream);
+}
+public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+int[] arr = (int[])obj;
+if (arr.length == 0) { stream.write((byte)'[', (byte)']'); return; }
+stream.writeArrayStart(); stream.writeIndention();
+int i = 0;
+int e = arr[i++];
+stream.writeVal((int)e);
+while (i < arr.length) {
+stream.writeMore();
+e = arr[i++];
+stream.writeVal((int)e);
+}
+stream.writeArrayEnd();
+}
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_com/example/myapplication/User.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_com/example/myapplication/User.java
new file mode 100644
index 00000000..10daee09
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_com/example/myapplication/User.java
@@ -0,0 +1,29 @@
+package jsoniter_codegen.cfg1173796797.encoder.java.util.List_com.example.myapplication;
+public class User implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((java.util.List)obj, stream);
+}
+public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+java.util.List list = (java.util.List)obj;
+int size = list.size();
+if (size == 0) { stream.write((byte)'[', (byte)']'); return; }
+stream.writeArrayStart(); stream.writeIndention();
+java.lang.Object e = list.get(0);
+if (e == null) { stream.writeNull(); } else {
+
+jsoniter_codegen.cfg1173796797.encoder.com.example.myapplication.User.encode_((com.example.myapplication.User)e, stream);
+
+}
+for (int i = 1; i < size; i++) {
+stream.writeMore();
+e = list.get(i);
+if (e == null) { stream.writeNull(); } else {
+
+jsoniter_codegen.cfg1173796797.encoder.com.example.myapplication.User.encode_((com.example.myapplication.User)e, stream);
+
+}
+}
+stream.writeArrayEnd();
+}
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_java/lang/Integer.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_java/lang/Integer.java
new file mode 100644
index 00000000..0e2069fb
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_java/lang/Integer.java
@@ -0,0 +1,25 @@
+package jsoniter_codegen.cfg1173796797.encoder.java.util.List_java.lang;
+public class Integer implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((java.util.List)obj, stream);
+}
+public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+java.util.List list = (java.util.List)obj;
+int size = list.size();
+if (size == 0) { stream.write((byte)'[', (byte)']'); return; }
+stream.writeArrayStart(); stream.writeIndention();
+java.lang.Object e = list.get(0);
+if (e == null) { stream.writeNull(); } else {
+stream.writeVal((java.lang.Integer)e);
+}
+for (int i = 1; i < size; i++) {
+stream.writeMore();
+e = list.get(i);
+if (e == null) { stream.writeNull(); } else {
+stream.writeVal((java.lang.Integer)e);
+}
+}
+stream.writeArrayEnd();
+}
+}
diff --git a/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/Map_java/lang/String_java/lang/Object.java b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/Map_java/lang/String_java/lang/Object.java
new file mode 100644
index 00000000..f2bd7203
--- /dev/null
+++ b/android-demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/Map_java/lang/String_java/lang/Object.java
@@ -0,0 +1,30 @@
+package jsoniter_codegen.cfg1173796797.encoder.java.util.Map_java.lang.String_java.lang;
+public class Object implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((java.util.Map)obj, stream);
+}
+public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+java.util.Map map = (java.util.Map)obj;
+java.util.Iterator iter = map.entrySet().iterator();
+if(!iter.hasNext()) { stream.write((byte)'{', (byte)'}'); return; }
+java.util.Map.Entry entry = (java.util.Map.Entry)iter.next();
+stream.writeObjectStart(); stream.writeIndention();
+stream.writeVal((java.lang.String)entry.getKey());
+stream.write((byte)':', (byte)' ');
+if (entry.getValue() == null) { stream.writeNull(); } else {
+stream.writeVal((java.lang.Object)entry.getValue());
+}
+while(iter.hasNext()) {
+entry = (java.util.Map.Entry)iter.next();
+stream.writeMore();
+stream.writeVal((java.lang.String)entry.getKey());
+stream.write((byte)':', (byte)' ');
+if (entry.getValue() == null) { stream.writeNull(); } else {
+stream.writeVal((java.lang.Object)entry.getValue());
+}
+}
+stream.writeObjectEnd();
+}
+}
diff --git a/android-demo/src/main/res/layout/activity_main.xml b/android-demo/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..e01d85ca
--- /dev/null
+++ b/android-demo/src/main/res/layout/activity_main.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/android-demo/src/main/res/mipmap-hdpi/ic_launcher.png b/android-demo/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..cde69bcc
Binary files /dev/null and b/android-demo/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android-demo/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android-demo/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9a078e3e
Binary files /dev/null and b/android-demo/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android-demo/src/main/res/mipmap-mdpi/ic_launcher.png b/android-demo/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..c133a0cb
Binary files /dev/null and b/android-demo/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android-demo/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android-demo/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..efc028a6
Binary files /dev/null and b/android-demo/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android-demo/src/main/res/mipmap-xhdpi/ic_launcher.png b/android-demo/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..bfa42f0e
Binary files /dev/null and b/android-demo/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android-demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android-demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..3af2608a
Binary files /dev/null and b/android-demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android-demo/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android-demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..324e72cd
Binary files /dev/null and b/android-demo/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android-demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android-demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9bec2e62
Binary files /dev/null and b/android-demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android-demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android-demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..aee44e13
Binary files /dev/null and b/android-demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android-demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android-demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..34947cd6
Binary files /dev/null and b/android-demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/android-demo/src/main/res/values/colors.xml b/android-demo/src/main/res/values/colors.xml
new file mode 100644
index 00000000..3ab3e9cb
--- /dev/null
+++ b/android-demo/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/android-demo/src/main/res/values/strings.xml b/android-demo/src/main/res/values/strings.xml
new file mode 100644
index 00000000..efd30732
--- /dev/null
+++ b/android-demo/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ My Application
+
diff --git a/android-demo/src/main/res/values/styles.xml b/android-demo/src/main/res/values/styles.xml
new file mode 100644
index 00000000..5885930d
--- /dev/null
+++ b/android-demo/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/android-demo/src/test/java/com/example/myapplication/ExampleUnitTest.java b/android-demo/src/test/java/com/example/myapplication/ExampleUnitTest.java
new file mode 100644
index 00000000..ff8f26e0
--- /dev/null
+++ b/android-demo/src/test/java/com/example/myapplication/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.myapplication;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/demo/pom.xml b/demo/pom.xml
index 7dd8c5fe..962f0c8e 100644
--- a/demo/pom.xml
+++ b/demo/pom.xml
@@ -2,9 +2,9 @@
4.0.0
com.jsoniter
- 0.9.5-SNAPSHOT
+ 0.9.21-SNAPSHOT
jsoniter-demo
- json iterator
+ json iterator demo
jsoniter (json-iterator) is fast and flexible JSON parser available in Java and Go
http://jsoniter.com
jar
@@ -49,7 +49,7 @@
com.jsoniter
jsoniter
- 0.9.6-SNAPSHOT
+ 0.9.21-SNAPSHOT
org.openjdk.jmh
@@ -76,6 +76,16 @@
dsl-json
1.3.2
+
+ org.apache.thrift
+ libthrift
+ 0.9.1
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.22
+
@@ -86,7 +96,16 @@
fastjson
1.2.22
-
+
+ com.squareup.moshi
+ moshi
+ 1.3.1
+
+
+ com.google.protobuf
+ protobuf-java
+ 3.2.0rc2
+
@@ -96,8 +115,8 @@
maven-compiler-plugin
3.6.0
- 1.8
- 1.8
+ 1.6
+ 1.6
UTF-8
@@ -118,7 +137,7 @@
-classpath
- com.jsoniter.StaticCodeGenerator
+ com.jsoniter.static_codegen.StaticCodegen
com.jsoniter.demo.DemoCodegenConfig
diff --git a/demo/src/main/java/com/jsoniter/demo/Demo.java b/demo/src/main/java/com/jsoniter/demo/Demo.java
index f1f5c5b1..a25650df 100644
--- a/demo/src/main/java/com/jsoniter/demo/Demo.java
+++ b/demo/src/main/java/com/jsoniter/demo/Demo.java
@@ -1,17 +1,15 @@
package com.jsoniter.demo;
-import com.jsoniter.DecodingMode;
import com.jsoniter.JsonIterator;
+import com.jsoniter.any.Any;
import com.jsoniter.output.EncodingMode;
import com.jsoniter.output.JsonStream;
+import com.jsoniter.spi.DecodingMode;
public class Demo {
static {
// ensure the jsoniter is properly setup
new DemoCodegenConfig().setup();
- JsonIterator.setMode(DecodingMode.STATIC_MODE);
- JsonStream.setMode(EncodingMode.STATIC_MODE);
- JsonStream.defaultIndentionStep = 2;
}
public static void main(String[] args) {
@@ -19,6 +17,7 @@ public static void main(String[] args) {
System.out.println(user.firstName);
System.out.println(user.lastName);
System.out.println(user.score);
+ user.attachment = Any.wrapArray(new int[]{1, 2, 3});
System.out.println(JsonStream.serialize(user));
}
}
diff --git a/demo/src/main/java/com/jsoniter/demo/DemoCodegenConfig.java b/demo/src/main/java/com/jsoniter/demo/DemoCodegenConfig.java
index d9e27acd..4c16249d 100644
--- a/demo/src/main/java/com/jsoniter/demo/DemoCodegenConfig.java
+++ b/demo/src/main/java/com/jsoniter/demo/DemoCodegenConfig.java
@@ -1,22 +1,29 @@
package com.jsoniter.demo;
import com.jsoniter.JsonIterator;
-import com.jsoniter.StaticCodeGenerator;
-import com.jsoniter.spi.CodegenConfig;
+import com.jsoniter.any.Any;
+import com.jsoniter.output.EncodingMode;
+import com.jsoniter.output.JsonStream;
import com.jsoniter.spi.Decoder;
+import com.jsoniter.spi.DecodingMode;
import com.jsoniter.spi.JsoniterSpi;
import com.jsoniter.spi.TypeLiteral;
+import com.jsoniter.static_codegen.StaticCodegenConfig;
import java.io.IOException;
import java.util.List;
import java.util.Map;
-public class DemoCodegenConfig implements CodegenConfig {
+public class DemoCodegenConfig implements StaticCodegenConfig {
@Override
public void setup() {
// register custom decoder or extensions before codegen
// so that we doing codegen, we know in which case, we need to callback
+ Any.registerEncoders();
+ JsonIterator.setMode(DecodingMode.STATIC_MODE);
+ JsonStream.setMode(EncodingMode.STATIC_MODE);
+ JsonStream.setIndentionStep(2);
JsoniterSpi.registerPropertyDecoder(User.class, "score", new Decoder.IntDecoder() {
@Override
public int decodeInt(JsonIterator iter) throws IOException {
@@ -41,8 +48,4 @@ public TypeLiteral[] whatToCodegen() {
TypeLiteral.create(User.class)
};
}
-
- public static void main(String[] args) throws Exception {
- StaticCodeGenerator.main(new String[]{DemoCodegenConfig.class.getCanonicalName()});
- }
}
diff --git a/demo/src/main/java/com/jsoniter/demo/User.java b/demo/src/main/java/com/jsoniter/demo/User.java
index 54beb169..9c4e5906 100644
--- a/demo/src/main/java/com/jsoniter/demo/User.java
+++ b/demo/src/main/java/com/jsoniter/demo/User.java
@@ -1,7 +1,13 @@
package com.jsoniter.demo;
+import com.jsoniter.annotation.JsonProperty;
+import com.jsoniter.any.Any;
+
public class User {
+ @JsonProperty(nullable = false)
public String firstName;
+ @JsonProperty(nullable = false)
public String lastName;
public int score;
+ public Any attachment;
}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/com/jsoniter/demo/User.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/com/jsoniter/demo/User.java
new file mode 100644
index 00000000..569ab3f9
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/com/jsoniter/demo/User.java
@@ -0,0 +1,60 @@
+package jsoniter_codegen.cfg1173796797.decoder.com.jsoniter.demo;
+public class User implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { java.lang.Object existingObj = com.jsoniter.CodegenAccess.resetExistingObject(iter);
+byte nextToken = com.jsoniter.CodegenAccess.readByte(iter);
+if (nextToken != '{') {
+if (nextToken == 'n') {
+com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);
+return null;
+} else {
+nextToken = com.jsoniter.CodegenAccess.nextToken(iter);
+if (nextToken == 'n') {
+com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);
+return null;
+}
+} // end of if null
+} // end of if {
+nextToken = com.jsoniter.CodegenAccess.readByte(iter);
+if (nextToken != '"') {
+if (nextToken == '}') {
+return (existingObj == null ? new com.jsoniter.demo.User() : (com.jsoniter.demo.User)existingObj);
+} else {
+nextToken = com.jsoniter.CodegenAccess.nextToken(iter);
+if (nextToken == '}') {
+return (existingObj == null ? new com.jsoniter.demo.User() : (com.jsoniter.demo.User)existingObj);
+} else {
+com.jsoniter.CodegenAccess.unreadByte(iter);
+}
+} // end of if end
+} else { com.jsoniter.CodegenAccess.unreadByte(iter); }// end of if not quote
+java.lang.String _firstName_ = null;
+java.lang.String _lastName_ = null;
+int _score_ = 0;
+com.jsoniter.any.Any _attachment_ = null;
+do {
+switch (com.jsoniter.CodegenAccess.readObjectFieldAsHash(iter)) {
+case -1513391000:
+_attachment_ = (com.jsoniter.any.Any)iter.readAny();
+continue;
+case -1078100014:
+_lastName_ = (java.lang.String)iter.readString();
+continue;
+case -799547430:
+_firstName_ = (java.lang.String)iter.readString();
+continue;
+case -768634731:
+_score_ = (int)com.jsoniter.CodegenAccess.readInt("score@jsoniter_codegen.cfg1173796797.decoder.com.jsoniter.demo.User", iter);
+continue;
+}
+iter.skip();
+} while (com.jsoniter.CodegenAccess.nextTokenIsComma(iter));
+com.jsoniter.demo.User obj = (existingObj == null ? new com.jsoniter.demo.User() : (com.jsoniter.demo.User)existingObj);
+obj.firstName = _firstName_;
+obj.lastName = _lastName_;
+obj.score = _score_;
+obj.attachment = _attachment_;
+return obj;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/int_array.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/int_array.java
new file mode 100644
index 00000000..9ff993e7
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/int_array.java
@@ -0,0 +1,60 @@
+package jsoniter_codegen.cfg1173796797.decoder;
+public class int_array implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { com.jsoniter.CodegenAccess.resetExistingObject(iter);
+byte nextToken = com.jsoniter.CodegenAccess.readByte(iter);
+if (nextToken != '[') {
+if (nextToken == 'n') {
+com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);
+com.jsoniter.CodegenAccess.resetExistingObject(iter); return null;
+} else {
+nextToken = com.jsoniter.CodegenAccess.nextToken(iter);
+if (nextToken == 'n') {
+com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);
+com.jsoniter.CodegenAccess.resetExistingObject(iter); return null;
+}
+}
+}
+nextToken = com.jsoniter.CodegenAccess.nextToken(iter);
+if (nextToken == ']') {
+return new int[0];
+}
+com.jsoniter.CodegenAccess.unreadByte(iter);
+int a1 = (int)iter.readInt();
+if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+return new int[]{ a1 };
+}
+int a2 = (int)iter.readInt();
+if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+return new int[]{ a1, a2 };
+}
+int a3 = (int)iter.readInt();
+if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+return new int[]{ a1, a2, a3 };
+}
+int a4 = (int) (int)iter.readInt();
+if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+return new int[]{ a1, a2, a3, a4 };
+}
+int a5 = (int) (int)iter.readInt();
+int[] arr = new int[10];
+arr[0] = a1;
+arr[1] = a2;
+arr[2] = a3;
+arr[3] = a4;
+arr[4] = a5;
+int i = 5;
+while (com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {
+if (i == arr.length) {
+int[] newArr = new int[arr.length * 2];
+System.arraycopy(arr, 0, newArr, 0, arr.length);
+arr = newArr;
+}
+arr[i++] = (int)iter.readInt();
+}
+int[] result = new int[i];
+System.arraycopy(arr, 0, result, 0, i);
+return result;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_com/jsoniter/demo/User.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_com/jsoniter/demo/User.java
new file mode 100644
index 00000000..c257a011
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_com/jsoniter/demo/User.java
@@ -0,0 +1,42 @@
+package jsoniter_codegen.cfg1173796797.decoder.java.util.List_com.jsoniter.demo;
+public class User implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { java.util.ArrayList col = (java.util.ArrayList)com.jsoniter.CodegenAccess.resetExistingObject(iter);
+if (iter.readNull()) { com.jsoniter.CodegenAccess.resetExistingObject(iter); return null; }
+if (!com.jsoniter.CodegenAccess.readArrayStart(iter)) {
+return col == null ? new java.util.ArrayList(0): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+}
+Object a1 = (com.jsoniter.demo.User)jsoniter_codegen.cfg1173796797.decoder.com.jsoniter.demo.User.decode_(iter);
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(1): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+return obj;
+}
+Object a2 = (com.jsoniter.demo.User)jsoniter_codegen.cfg1173796797.decoder.com.jsoniter.demo.User.decode_(iter);
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(2): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+return obj;
+}
+Object a3 = (com.jsoniter.demo.User)jsoniter_codegen.cfg1173796797.decoder.com.jsoniter.demo.User.decode_(iter);
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(3): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+obj.add(a3);
+return obj;
+}
+Object a4 = (com.jsoniter.demo.User)jsoniter_codegen.cfg1173796797.decoder.com.jsoniter.demo.User.decode_(iter);
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(8): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+obj.add(a3);
+obj.add(a4);
+while (com.jsoniter.CodegenAccess.nextToken(iter) == ',') {
+obj.add((com.jsoniter.demo.User)jsoniter_codegen.cfg1173796797.decoder.com.jsoniter.demo.User.decode_(iter));
+}
+return obj;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_java/lang/Integer.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_java/lang/Integer.java
new file mode 100644
index 00000000..419b2413
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/List_java/lang/Integer.java
@@ -0,0 +1,42 @@
+package jsoniter_codegen.cfg1173796797.decoder.java.util.List_java.lang;
+public class Integer implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { java.util.ArrayList col = (java.util.ArrayList)com.jsoniter.CodegenAccess.resetExistingObject(iter);
+if (iter.readNull()) { com.jsoniter.CodegenAccess.resetExistingObject(iter); return null; }
+if (!com.jsoniter.CodegenAccess.readArrayStart(iter)) {
+return col == null ? new java.util.ArrayList(0): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+}
+Object a1 = (java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt()));
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(1): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+return obj;
+}
+Object a2 = (java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt()));
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(2): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+return obj;
+}
+Object a3 = (java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt()));
+if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(3): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+obj.add(a3);
+return obj;
+}
+Object a4 = (java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt()));
+java.util.ArrayList obj = col == null ? new java.util.ArrayList(8): (java.util.ArrayList)com.jsoniter.CodegenAccess.reuseCollection(col);
+obj.add(a1);
+obj.add(a2);
+obj.add(a3);
+obj.add(a4);
+while (com.jsoniter.CodegenAccess.nextToken(iter) == ',') {
+obj.add((java.lang.Integer)(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt())));
+}
+return obj;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/Map_java/lang/String_java/lang/Object.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/Map_java/lang/String_java/lang/Object.java
new file mode 100644
index 00000000..3250faa4
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/decoder/java/util/Map_java/lang/String_java/lang/Object.java
@@ -0,0 +1,17 @@
+package jsoniter_codegen.cfg1173796797.decoder.java.util.Map_java.lang.String_java.lang;
+public class Object implements com.jsoniter.spi.Decoder {
+public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { java.util.HashMap map = (java.util.HashMap)com.jsoniter.CodegenAccess.resetExistingObject(iter);
+if (iter.readNull()) { return null; }
+if (map == null) { map = new java.util.HashMap(); }
+if (!com.jsoniter.CodegenAccess.readObjectStart(iter)) {
+return map;
+}
+do {
+java.lang.Object mapKey = com.jsoniter.CodegenAccess.readObjectFieldAsString(iter);
+map.put(mapKey, (java.lang.Object)iter.read());
+} while (com.jsoniter.CodegenAccess.nextToken(iter) == ',');
+return map;
+}public java.lang.Object decode(com.jsoniter.JsonIterator iter) throws java.io.IOException {
+return decode_(iter);
+}
+}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/com/jsoniter/demo/User.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/com/jsoniter/demo/User.java
new file mode 100644
index 00000000..03ccb36e
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/com/jsoniter/demo/User.java
@@ -0,0 +1,25 @@
+package jsoniter_codegen.cfg1173796797.encoder.com.jsoniter.demo;
+public class User implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((com.jsoniter.demo.User)obj, stream);
+}
+public static void encode_(com.jsoniter.demo.User obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+stream.writeObjectStart();
+stream.writeIndention();
+stream.writeObjectField("firstName");
+stream.writeVal((java.lang.String)obj.firstName);
+stream.writeMore();
+stream.writeObjectField("lastName");
+stream.writeVal((java.lang.String)obj.lastName);
+stream.writeMore();
+stream.writeObjectField("score");
+stream.writeVal((int)obj.score);
+stream.writeMore();
+stream.writeObjectField("attachment");
+if (obj.attachment == null) { stream.writeNull(); } else {
+stream.writeVal((com.jsoniter.any.Any)obj.attachment);
+}
+stream.writeObjectEnd();
+}
+}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/int_array.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/int_array.java
new file mode 100644
index 00000000..e799b768
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/int_array.java
@@ -0,0 +1,21 @@
+package jsoniter_codegen.cfg1173796797.encoder;
+public class int_array implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((int[])obj, stream);
+}
+public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+int[] arr = (int[])obj;
+if (arr.length == 0) { stream.write((byte)'[', (byte)']'); return; }
+stream.writeArrayStart(); stream.writeIndention();
+int i = 0;
+int e = arr[i++];
+stream.writeVal((int)e);
+while (i < arr.length) {
+stream.writeMore();
+e = arr[i++];
+stream.writeVal((int)e);
+}
+stream.writeArrayEnd();
+}
+}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_com/jsoniter/demo/User.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_com/jsoniter/demo/User.java
new file mode 100644
index 00000000..ab31cdd9
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_com/jsoniter/demo/User.java
@@ -0,0 +1,29 @@
+package jsoniter_codegen.cfg1173796797.encoder.java.util.List_com.jsoniter.demo;
+public class User implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((java.util.List)obj, stream);
+}
+public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+java.util.List list = (java.util.List)obj;
+int size = list.size();
+if (size == 0) { stream.write((byte)'[', (byte)']'); return; }
+stream.writeArrayStart(); stream.writeIndention();
+java.lang.Object e = list.get(0);
+if (e == null) { stream.writeNull(); } else {
+
+jsoniter_codegen.cfg1173796797.encoder.com.jsoniter.demo.User.encode_((com.jsoniter.demo.User)e, stream);
+
+}
+for (int i = 1; i < size; i++) {
+stream.writeMore();
+e = list.get(i);
+if (e == null) { stream.writeNull(); } else {
+
+jsoniter_codegen.cfg1173796797.encoder.com.jsoniter.demo.User.encode_((com.jsoniter.demo.User)e, stream);
+
+}
+}
+stream.writeArrayEnd();
+}
+}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_java/lang/Integer.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_java/lang/Integer.java
new file mode 100644
index 00000000..0e2069fb
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/List_java/lang/Integer.java
@@ -0,0 +1,25 @@
+package jsoniter_codegen.cfg1173796797.encoder.java.util.List_java.lang;
+public class Integer implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((java.util.List)obj, stream);
+}
+public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+java.util.List list = (java.util.List)obj;
+int size = list.size();
+if (size == 0) { stream.write((byte)'[', (byte)']'); return; }
+stream.writeArrayStart(); stream.writeIndention();
+java.lang.Object e = list.get(0);
+if (e == null) { stream.writeNull(); } else {
+stream.writeVal((java.lang.Integer)e);
+}
+for (int i = 1; i < size; i++) {
+stream.writeMore();
+e = list.get(i);
+if (e == null) { stream.writeNull(); } else {
+stream.writeVal((java.lang.Integer)e);
+}
+}
+stream.writeArrayEnd();
+}
+}
diff --git a/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/Map_java/lang/String_java/lang/Object.java b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/Map_java/lang/String_java/lang/Object.java
new file mode 100644
index 00000000..f2bd7203
--- /dev/null
+++ b/demo/src/main/java/jsoniter_codegen/cfg1173796797/encoder/java/util/Map_java/lang/String_java/lang/Object.java
@@ -0,0 +1,30 @@
+package jsoniter_codegen.cfg1173796797.encoder.java.util.Map_java.lang.String_java.lang;
+public class Object implements com.jsoniter.spi.Encoder {
+public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+encode_((java.util.Map)obj, stream);
+}
+public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {
+if (obj == null) { stream.writeNull(); return; }
+java.util.Map map = (java.util.Map)obj;
+java.util.Iterator iter = map.entrySet().iterator();
+if(!iter.hasNext()) { stream.write((byte)'{', (byte)'}'); return; }
+java.util.Map.Entry entry = (java.util.Map.Entry)iter.next();
+stream.writeObjectStart(); stream.writeIndention();
+stream.writeVal((java.lang.String)entry.getKey());
+stream.write((byte)':', (byte)' ');
+if (entry.getValue() == null) { stream.writeNull(); } else {
+stream.writeVal((java.lang.Object)entry.getValue());
+}
+while(iter.hasNext()) {
+entry = (java.util.Map.Entry)iter.next();
+stream.writeMore();
+stream.writeVal((java.lang.String)entry.getKey());
+stream.write((byte)':', (byte)' ');
+if (entry.getValue() == null) { stream.writeNull(); } else {
+stream.writeVal((java.lang.Object)entry.getValue());
+}
+}
+stream.writeObjectEnd();
+}
+}
diff --git a/demo/src/test/java/com/jsoniter/demo/ArrayBinding.java b/demo/src/test/java/com/jsoniter/demo/ArrayBinding.java
deleted file mode 100644
index 560a9f2a..00000000
--- a/demo/src/test/java/com/jsoniter/demo/ArrayBinding.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package com.jsoniter.demo;
-
-import com.dslplatform.json.DslJson;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.annotation.JacksonAnnotationSupport;
-import com.jsoniter.spi.TypeLiteral;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.IOException;
-
-@State(Scope.Thread)
-public class ArrayBinding {
- private TypeLiteral typeLiteral;
- private ObjectMapper jackson;
- private byte[] input;
- private TypeReference typeRef;
- private String inputStr;
-
- private JsonIterator iter;
- private DslJson dslJson;
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- inputStr = "[1,2,3,4,5,6,7,8,9]".replace('\'', '"');
- input = inputStr.getBytes();
- iter = JsonIterator.parse(input);
- typeLiteral = new TypeLiteral() {
- };
- typeRef = new TypeReference() {
- };
- JacksonAnnotationSupport.enable();
- jackson = new ObjectMapper();
- jackson.registerModule(new AfterburnerModule());
- dslJson = new DslJson();
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- System.out.println(withJsoniter());
- System.out.println(withIterator());
- System.out.println(withJackson());
- System.out.println(withDsljson());
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "ArrayBinding",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Benchmark
- public void withJsoniterBinding(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJsoniterIterator(Blackhole bh) throws IOException {
- bh.consume(withIterator());
- }
-
- @Benchmark
- public void withJackson(Blackhole bh) throws IOException {
- bh.consume(withJackson());
- }
-
- @Benchmark
- public void withDsljson(Blackhole bh) throws IOException {
- bh.consume(withDsljson());
- }
-
- private int withJsoniter() throws IOException {
- iter.reset(input);
- int[] arr = iter.read(typeLiteral);
- int total = 0;
- for (int i = 0; i < arr.length; i++) {
- total += arr[i];
- }
- return total;
- }
-
- private int withJackson() throws IOException {
- int[] arr = jackson.readValue(input, typeRef);
- int total = 0;
- for (int i = 0; i < arr.length; i++) {
- total += arr[i];
- }
- return total;
- }
-
- private int withDsljson() throws IOException {
- int[] arr = (int[]) dslJson.deserialize(int[].class, input, input.length);
- int total = 0;
- for (int i = 0; i < arr.length; i++) {
- total += arr[i];
- }
- return total;
- }
-
- private int withIterator() throws IOException {
- iter.reset(input);
- int total = 0;
- while (iter.readArray()) {
- total += iter.readInt();
- }
- return total;
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/ConstructorBinding.java b/demo/src/test/java/com/jsoniter/demo/ConstructorBinding.java
deleted file mode 100644
index d18225b3..00000000
--- a/demo/src/test/java/com/jsoniter/demo/ConstructorBinding.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package com.jsoniter.demo;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
-import com.jsoniter.DecodingMode;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.ReflectionDecoderFactory;
-import com.jsoniter.annotation.JacksonAnnotationSupport;
-import com.jsoniter.spi.JsoniterSpi;
-import com.jsoniter.spi.TypeLiteral;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.IOException;
-
-@State(Scope.Thread)
-public class ConstructorBinding {
-
- private TypeLiteral typeLiteral;
- private ObjectMapper jackson;
- private byte[] input;
- private TypeReference typeRef;
- private String inputStr;
-
- public static class TestObject {
- @JsonIgnore
- private int field1;
- @JsonIgnore
- private int field2;
-
- @JsonCreator
- public TestObject(
- @JsonProperty("field1") int field1,
- @JsonProperty("field2") int field2) {
- this.field1 = field1;
- this.field2 = field2;
- }
-
- @Override
- public String toString() {
- return "TestObject1{" +
- "field1=" + field1 +
- ", field2=" + field2 +
- '}';
- }
- }
-
-
- private JsonIterator iter;
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- inputStr = "{'field1':100,'field2':101}";
- input = inputStr.replace('\'', '"').getBytes();
- iter = JsonIterator.parse(input);
- typeLiteral = new TypeLiteral() {
- };
- typeRef = new TypeReference() {
- };
- JacksonAnnotationSupport.enable();
- jackson = new ObjectMapper();
- jackson.registerModule(new AfterburnerModule());
- if (params != null) {
- if (params.getBenchmark().contains("withJsoniterStrictMode")) {
- JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY);
- }
- if (params.getBenchmark().contains("withJsoniterReflection")) {
- JsoniterSpi.registerTypeDecoder(TestObject.class, ReflectionDecoderFactory.create(TestObject.class));
- }
- }
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- JsoniterSpi.registerTypeDecoder(TestObject.class, ReflectionDecoderFactory.create(TestObject.class));
- System.out.println(withJsoniter());
- System.out.println(withJackson());
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "ConstructorBinding",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Benchmark
- public void withJsoniterHashMode(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJsoniterStrictMode(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJsoniterReflection(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJackson(Blackhole bh) throws IOException {
- bh.consume(withJackson());
- }
-
- private TestObject withJsoniter() throws IOException {
- iter.reset(input);
- return iter.read(typeLiteral);
- }
-
- private TestObject withJackson() throws IOException {
- return jackson.readValue(input, typeRef);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/FieldMatching.java b/demo/src/test/java/com/jsoniter/demo/FieldMatching.java
deleted file mode 100644
index 371b9911..00000000
--- a/demo/src/test/java/com/jsoniter/demo/FieldMatching.java
+++ /dev/null
@@ -1,134 +0,0 @@
-package com.jsoniter.demo;
-
-import com.jsoniter.DecodingMode;
-import com.jsoniter.JsonException;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.annotation.JsonProperty;
-import com.jsoniter.annotation.JsonObject;
-import com.jsoniter.annotation.JsoniterAnnotationSupport;
-import com.jsoniter.spi.TypeLiteral;
-import org.junit.Assert;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.IOException;
-
-@State(Scope.Thread)
-public class FieldMatching {
- private TypeLiteral testObject0Type;
- private TypeLiteral testObject1Type;
- private TypeLiteral testObject2Type;
- private TypeLiteral testObject3Type;
- private TypeLiteral testObject4Type;
- private JsonIterator iter0;
- private JsonIterator iter1Success;
- private byte[] iter0Input;
- private byte[] iter1SuccessInput;
-
- public static class TestObject0 {
- public int field1;
- public int field2;
- public int field3;
- }
-
- public static class TestObject1 {
- @JsonProperty(required = true)
- public int field1;
- @JsonProperty(required = true)
- public int field2;
- @JsonProperty(required = true)
- public int field3;
- }
-
- @JsonObject(asExtraForUnknownProperties = true)
- public static class TestObject2 {
- public int field1;
- public int field2;
- }
-
- @JsonObject(asExtraForUnknownProperties = true, unknownPropertiesWhitelist = {"field2"})
- public static class TestObject3 {
- public int field1;
- }
-
- @JsonObject(unknownPropertiesBlacklist = {"field3"})
- public static class TestObject4 {
- public int field1;
- }
-
- @Setup(Level.Trial)
- public void benchSetup() {
- JsoniterAnnotationSupport.enable();
- JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY);
- iter0Input = "{'field1':101,'field2':101,'field3':101}".replace('\'', '"').getBytes();
- iter0 = JsonIterator.parse(iter0Input);
- iter1SuccessInput = "{'field1':101,'field2':101,'field3':101}".replace('\'', '"').getBytes();
- iter1Success = JsonIterator.parse(iter1SuccessInput);
- testObject0Type = new TypeLiteral() {
- };
- testObject1Type = new TypeLiteral() {
- };
- testObject2Type = new TypeLiteral() {
- };
- testObject3Type = new TypeLiteral() {
- };
- testObject4Type = new TypeLiteral() {
- };
- }
-
- @Test
- public void test() throws IOException {
- benchSetup();
- try {
- JsonIterator iter1Failure = JsonIterator.parse("{'field2':101}".replace('\'', '"').getBytes());
- iter1Failure.read(testObject1Type);
- Assert.fail();
- } catch (JsonException e) {
- System.out.println(e);
- }
- try {
- JsonIterator iter2Failure = JsonIterator.parse("{'field1':101,'field2':101,'field3':101}".replace('\'', '"').getBytes());
- iter2Failure.read(testObject2Type);
- Assert.fail();
- } catch (JsonException e) {
- System.out.println(e);
- }
- try {
- JsonIterator iter3Failure = JsonIterator.parse("{'field1':101,'field2':101,'field3':101}".replace('\'', '"').getBytes());
- iter3Failure.read(testObject3Type);
- Assert.fail();
- } catch (JsonException e) {
- System.out.println(e);
- }
- try {
- JsonIterator iter4Failure = JsonIterator.parse("{'field1':101,'field2':101,'field3':101}".replace('\'', '"').getBytes());
- iter4Failure.read(testObject4Type);
- Assert.fail();
- } catch (JsonException e) {
- System.out.println(e);
- }
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "FieldMatching",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Benchmark
- public void iter0(Blackhole bh) throws IOException {
- iter0.reset(iter0Input);
- bh.consume(iter0.read(testObject0Type));
- }
-
- @Benchmark
- public void iter1Success(Blackhole bh) throws IOException {
- iter1Success.reset(iter1SuccessInput);
- bh.consume(iter1Success.read(testObject1Type));
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/FloatOutput.java b/demo/src/test/java/com/jsoniter/demo/FloatOutput.java
deleted file mode 100644
index a4bfc23f..00000000
--- a/demo/src/test/java/com/jsoniter/demo/FloatOutput.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.jsoniter.demo;
-
-
-import com.dslplatform.json.DslJson;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.jsoniter.output.JsonStream;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-@State(Scope.Thread)
-public class FloatOutput {
-
- private ByteArrayOutputStream baos;
- private ObjectMapper objectMapper;
- private JsonStream stream;
- private byte[] buffer;
- private DslJson dslJson;
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "FloatOutput",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- jsoniter();
- System.out.println(baos.toString());
- jackson();
- System.out.println(baos.toString());
- dsljson();
- System.out.println(baos.toString());
- }
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- baos = new ByteArrayOutputStream(1024 * 64);
- objectMapper = new ObjectMapper();
- objectMapper.getFactory().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
- stream = new JsonStream(baos, 4096);
- buffer = new byte[4096];
- dslJson = new DslJson();
- }
-
- @Benchmark
- public void jsoniter() throws IOException {
- baos.reset();
- stream.reset(baos);
- stream.writeVal(10.24f);
- stream.flush();
- }
-
- @Benchmark
- public void jackson() throws IOException {
- baos.reset();
- objectMapper.writeValue(baos, 10.24f);
- }
-
- @Benchmark
- public void dsljson() throws IOException {
- baos.reset();
- dslJson.serialize(10.24f, baos);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/IntegerOutput.java b/demo/src/test/java/com/jsoniter/demo/IntegerOutput.java
deleted file mode 100644
index 66a98438..00000000
--- a/demo/src/test/java/com/jsoniter/demo/IntegerOutput.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.jsoniter.demo;
-
-
-import com.dslplatform.json.DslJson;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.jsoniter.output.JsonStream;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-@State(Scope.Thread)
-public class IntegerOutput {
-
- private ByteArrayOutputStream baos;
- private ObjectMapper objectMapper;
- private JsonStream stream;
- private byte[] buffer;
- private DslJson dslJson;
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "IntegerOutput",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- jsoniter();
- System.out.println(baos.toString());
- jackson();
- System.out.println(baos.toString());
- dsljson();
- System.out.println(baos.toString());
- }
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- baos = new ByteArrayOutputStream(1024 * 64);
- objectMapper = new ObjectMapper();
- objectMapper.getFactory().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
- stream = new JsonStream(baos, 4096);
- buffer = new byte[4096];
- dslJson = new DslJson();
- }
-
- @Benchmark
- public void jsoniter() throws IOException {
- baos.reset();
- stream.reset(baos);
- stream.writeVal(1024);
- stream.flush();
- }
-
- @Benchmark
- public void jackson() throws IOException {
- baos.reset();
- objectMapper.writeValue(baos, 1024);
- }
-
- @Benchmark
- public void dsljson() throws IOException {
- baos.reset();
- dslJson.serialize(1024, baos);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/JmhFlightRecorderProfiler.java b/demo/src/test/java/com/jsoniter/demo/JmhFlightRecorderProfiler.java
deleted file mode 100644
index 4fc92ec8..00000000
--- a/demo/src/test/java/com/jsoniter/demo/JmhFlightRecorderProfiler.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.jsoniter.demo;
-
-import java.io.File;
-import java.lang.management.ManagementFactory;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.profile.ExternalProfiler;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.Aggregator;
-import org.openjdk.jmh.results.BenchmarkResult;
-import org.openjdk.jmh.results.Result;
-import org.openjdk.jmh.results.ResultRole;
-
-/**
- *
- * @author zoly
- */
-public final class JmhFlightRecorderProfiler implements ExternalProfiler {
-
- private static final String DUMP_FOLDER = System.getProperty("jmh.stack.profiles", "/tmp");
-
- private static final String DEFAULT_OPTIONS = System.getProperty("jmh.fr.options",
- "defaultrecording=true,settings=profile");
-
-
-
- @Override
- public Collection addJVMInvokeOptions(final BenchmarkParams params) {
- return Collections.emptyList();
- }
-
- private volatile String dumpFile;
-
- private static volatile String benchmarkName;
-
- public static String benchmarkName() {
- return benchmarkName;
- }
-
-
- /**
- * See:
- * http://docs.oracle.com/cd/E15289_01/doc.40/e15070/usingjfr.htm
- * and
- * http://docs.oracle.com/cd/E15289_01/doc.40/e15070/config_rec_data.htm
- * @param params
- * @return
- */
- @Override
- public Collection addJVMOptions(final BenchmarkParams params) {
- final String id = params.id();
- benchmarkName = id;
- dumpFile = DUMP_FOLDER + '/' + id + ".jfr";
- String flightRecorderOptions = DEFAULT_OPTIONS + ",dumponexit=true,dumponexitpath=" + dumpFile;
- return Arrays.asList(
- "-XX:+FlightRecorder",
- "-XX:FlightRecorderOptions=" + flightRecorderOptions);
- }
-
- @Override
- public void beforeTrial(final BenchmarkParams benchmarkParams) {
- final List inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
-// if (new Version(org.spf4j.base.Runtime.JAVA_VERSION).compareTo(new Version("1.8.0_40")) <= 0
-// && !inputArguments.contains("-XX:+UnlockCommercialFeatures")) {
-// throw new RuntimeException("-XX:+UnlockCommercialFeatures must pre present in the JVM options,"
-// + " current options are: " + inputArguments);
-// }
- }
-
-
- @Override
- public boolean allowPrintOut() {
- return true;
- }
-
- @Override
- public boolean allowPrintErr() {
- return false;
- }
-
-
- @Override
- public String getDescription() {
- return "Java Flight Recording profiler runs for every benchmark.";
- }
-
- @Override
- public Collection extends Result> afterTrial(final BenchmarkResult bp, final long l,
- final File file, final File file1) {
- NoResult r = new NoResult("Profile saved to " + dumpFile + ", results: " + bp
- + ", stdOutFile = " + file + ", stdErrFile = " + file1);
- return Collections.singleton(r);
- }
-
- private static final class NoResult extends Result {
- private static final long serialVersionUID = 1L;
-
- private final String output;
-
- NoResult(final String output) {
- super(ResultRole.SECONDARY, "JFR", of(Double.NaN), "N/A", AggregationPolicy.SUM);
- this.output = output;
- }
-
- @Override
- protected Aggregator getThreadAggregator() {
- return new NoResultAggregator();
- }
-
- @Override
- protected Aggregator getIterationAggregator() {
- return new NoResultAggregator();
- }
-
- private static class NoResultAggregator implements Aggregator {
-
- @Override
- public NoResult aggregate(final Collection results) {
- StringBuilder agg = new StringBuilder();
- for (NoResult r : results) {
- agg.append(r.output);
- }
- return new NoResult(agg.toString());
- }
- }
- }
-
- @Override
- public String toString() {
- return "JmhFlightRecorderProfiler{" + "dumpFile=" + dumpFile + '}';
- }
-
-}
\ No newline at end of file
diff --git a/demo/src/test/java/com/jsoniter/demo/LazyAny.java b/demo/src/test/java/com/jsoniter/demo/LazyAny.java
deleted file mode 100644
index 6ff9ebda..00000000
--- a/demo/src/test/java/com/jsoniter/demo/LazyAny.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.jsoniter.demo;
-
-import com.jsoniter.any.Any;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.Slice;
-import com.jsoniter.output.JsonStream;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-@State(Scope.Thread)
-public class LazyAny {
-
- private JsonIterator iter;
- private Slice input;
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) throws IOException {
- InputStream resourceAsStream = LazyAny.class.getResourceAsStream("/large.json");
- byte[] buf = new byte[32 * 1024];
- int size = resourceAsStream.read(buf);
- input = new Slice(buf, 0, size);
- iter = new JsonIterator();
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "LazyAny",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- "-prof", "stack",
- });
- }
-
- public static class User {
- public int index;
- public String name;
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- System.out.println(jsoniter());
- System.out.println(jsoniter_object());
-
- User tom = new User();
- tom.index = 1;
- tom.name = "tom";
- Map tomAsMap = Any.wrap(tom).asMap();
- tomAsMap.put("age", Any.wrap(17));
- System.out.println(JsonStream.serialize(tomAsMap));
- }
-
- @Benchmark
- public void jsoniter(Blackhole bh) throws IOException {
- bh.consume(jsoniter());
- }
-
- @Benchmark
- public void jsoniter_object(Blackhole bh) throws IOException {
- bh.consume(jsoniter_object());
- }
-
- public int jsoniter() throws IOException {
- iter.reset(input);
- Any users = iter.readAny();
- int total = 0;
- for (Any user : users) {
- total += user.get("friends").size();
- }
- return total;
- }
-
- public int jsoniter_object() throws IOException {
- iter.reset(input);
- List users = (List) iter.read();
- int total = 0;
- for (Object userObj : users) {
- Map user = (Map) userObj;
- List friends = (List) user.get("friends");
- total += friends.size();
- }
- return total;
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/ListBinding.java b/demo/src/test/java/com/jsoniter/demo/ListBinding.java
deleted file mode 100644
index a8854000..00000000
--- a/demo/src/test/java/com/jsoniter/demo/ListBinding.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.jsoniter.demo;
-
-import com.dslplatform.json.DslJson;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.annotation.JacksonAnnotationSupport;
-import com.jsoniter.spi.TypeLiteral;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.IOException;
-import java.util.List;
-
-@State(Scope.Thread)
-public class ListBinding {
- private TypeLiteral> typeLiteral;
- private ObjectMapper jackson;
- private byte[] input;
- private TypeReference> typeRef;
- private String inputStr;
-
- private JsonIterator iter;
- private DslJson dslJson;
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- inputStr = "['jackson','jsoniter','fastjson']".replace('\'', '"');
- input = inputStr.getBytes();
- iter = JsonIterator.parse(input);
- typeLiteral = new TypeLiteral>() {
- };
- typeRef = new TypeReference>() {
- };
- JacksonAnnotationSupport.enable();
- jackson = new ObjectMapper();
- jackson.registerModule(new AfterburnerModule());
- dslJson = new DslJson();
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- System.out.println(withJsoniter());
- System.out.println(withJackson());
- System.out.println(withDsljson());
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "ListBinding",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Benchmark
- public void withJsoniterBinding(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJackson(Blackhole bh) throws IOException {
- bh.consume(withJackson());
- }
-
- @Benchmark
- public void withDsljson(Blackhole bh) throws IOException {
- bh.consume(withDsljson());
- }
-
- private List withJsoniter() throws IOException {
- iter.reset(input);
- return iter.read(typeLiteral);
- }
-
- private List withJackson() throws IOException {
- return jackson.readValue(input, typeRef);
- }
-
- private List withDsljson() throws IOException {
- return (List) dslJson.deserializeList(String.class, input, input.length);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/MapBinding.java b/demo/src/test/java/com/jsoniter/demo/MapBinding.java
deleted file mode 100644
index d4b83e21..00000000
--- a/demo/src/test/java/com/jsoniter/demo/MapBinding.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package com.jsoniter.demo;
-
-
-import com.dslplatform.json.DslJson;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.annotation.JacksonAnnotationSupport;
-import com.jsoniter.spi.TypeLiteral;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Map;
-
-@State(Scope.Thread)
-public class MapBinding {
- private TypeLiteral> typeLiteral;
- private ObjectMapper jackson;
- private byte[] input;
- private TypeReference> typeRef;
- private String inputStr;
-
- private JsonIterator iter;
- private DslJson dslJson;
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- inputStr = "{'jsoniter':53.9123,'jackson':-8772.2131,'dsljson':99877}".replace('\'', '"');
- input = inputStr.getBytes();
- iter = JsonIterator.parse(input);
- typeLiteral = new TypeLiteral>() {
- };
- typeRef = new TypeReference>() {
- };
- JacksonAnnotationSupport.enable();
- jackson = new ObjectMapper();
- jackson.registerModule(new AfterburnerModule());
- dslJson = new DslJson();
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- System.out.println(withJsoniter());
- System.out.println(withJackson());
- System.out.println(withDsljson());
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "MapBinding",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Benchmark
- public void withJsoniterBinding(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJackson(Blackhole bh) throws IOException {
- bh.consume(withJackson());
- }
-
- @Benchmark
- public void withDsljson(Blackhole bh) throws IOException {
- bh.consume(withDsljson());
- }
-
- private Map withJsoniter() throws IOException {
- iter.reset(input);
- return iter.read(typeLiteral);
- }
-
- private Map withJackson() throws IOException {
- return jackson.readValue(input, typeRef);
- }
-
- private Map withDsljson() throws IOException {
- return (Map) dslJson.deserialize(Map.class, input, input.length);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/ModelTest.java b/demo/src/test/java/com/jsoniter/demo/ModelTest.java
deleted file mode 100644
index 30c17894..00000000
--- a/demo/src/test/java/com/jsoniter/demo/ModelTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package com.jsoniter.demo;
-
-import com.alibaba.fastjson.JSON;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
-import com.jsoniter.DecodingMode;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.spi.TypeLiteral;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.infra.Blackhole;
-import org.openjdk.jmh.runner.RunnerException;
-
-import java.io.IOException;
-
-// Benchmark Mode Cnt Score Error Units
-// ModelTest.fastjson thrpt 5 7790201.506 ± 260185.529 ops/s
-// ModelTest.jackson thrpt 5 4063696.579 ± 169609.697 ops/s
-// ModelTest.jsoniter thrpt 5 16392968.819 ± 197563.536 ops/s
-@State(Scope.Thread)
-public class ModelTest {
-
- private String input;
- private JsonIterator iter;
- private byte[] inputBytes;
- private TypeLiteral modelTypeLiteral; // this is thread-safe can reused
- private ObjectMapper jackson;
- private TypeReference modelTypeReference;
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
-// JsonIterator.enableStreamingSupport();
- input = "{\"name\":\"wenshao\",\"id\":1001}";
- inputBytes = input.getBytes();
- iter = new JsonIterator();
- modelTypeLiteral = new TypeLiteral() {
- };
- JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH);
- jackson = new ObjectMapper();
- jackson.registerModule(new AfterburnerModule());
- modelTypeReference = new TypeReference() {
- };
- }
-
- public static void main(String[] args) throws IOException, RunnerException {
- Main.main(new String[]{
- "ModelTest",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
-// "-jvmArgsAppend", "-server -XX:+DoEscapeAnalysis",
- });
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- iter.reset(inputBytes);
- System.out.println(iter.read(modelTypeLiteral).name);
- }
-
-// public static void main(String[] args) throws Exception {
-// Options opt = new OptionsBuilder()
-// .include("ModelTest")
-// .addProfiler(JmhFlightRecorderProfiler.class)
-// .jvmArgs("-Xmx512m", "-Xms512m", "-XX:+UnlockCommercialFeatures",
-// "-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintAssembly",
-// "-Djmh.stack.profiles=" + "/tmp",
-// "-Djmh.executor=FJP",
-// "-Djmh.fr.options=defaultrecording=true,settings=profile")
-// .warmupIterations(5)
-// .measurementTime(TimeValue.seconds(5))
-// .measurementIterations(5)
-// .forks(1)
-// .build();
-// new Runner(opt).run();
-// }
-
- @Benchmark
- public void jsoniter(Blackhole bh) throws IOException {
- iter.reset(inputBytes);
- bh.consume(iter.read(modelTypeLiteral));
- }
-
-// @Benchmark
- public void jsoniter_easy_mode(Blackhole bh) throws IOException {
- bh.consume(JsonIterator.deserialize(inputBytes, Model.class));
- }
-
- @Benchmark
- public void fastjson(Blackhole bh) throws IOException {
- // this is not a exactly fair comparison,
- // as string => object is not
- // bytes => object
- bh.consume(JSON.parseObject(input, Model.class));
- }
-
-// @Benchmark
- public void jackson(Blackhole bh) throws IOException {
- bh.consume(jackson.readValue(inputBytes, modelTypeReference));
- }
-
- public static class Model {
- public int id;
- public String name;
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/ObjectOutput.java b/demo/src/test/java/com/jsoniter/demo/ObjectOutput.java
deleted file mode 100644
index 510a5373..00000000
--- a/demo/src/test/java/com/jsoniter/demo/ObjectOutput.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.jsoniter.demo;
-
-
-import com.dslplatform.json.CompiledJson;
-import com.dslplatform.json.DslJson;
-import com.dslplatform.json.JsonWriter;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.jsoniter.output.EncodingMode;
-import com.jsoniter.output.JsonStream;
-import com.jsoniter.spi.TypeLiteral;
-import json.ExternalSerialization;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-@State(Scope.Thread)
-public class ObjectOutput {
-
- private ByteArrayOutputStream baos;
- private ObjectMapper objectMapper;
- private JsonStream stream;
- private byte[] buffer;
- private DslJson dslJson;
- private TestObject testObject;
- private TypeLiteral typeLiteral;
- private JsonWriter jsonWriter;
-
- @CompiledJson
- public static class TestObject {
- public String field1;
- public String field2;
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "ObjectOutput",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- jsoniter();
- System.out.println(baos.toString());
- jackson();
- System.out.println(baos.toString());
- dsljson();
- System.out.println(baos.toString());
- System.out.println(JsonStream.serialize(testObject));
- }
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- baos = new ByteArrayOutputStream(1024 * 64);
- objectMapper = new ObjectMapper();
- objectMapper.getFactory().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
- JsonStream.setMode(EncodingMode.DYNAMIC_MODE);
- stream = new JsonStream(baos, 4096);
- buffer = new byte[4096];
- dslJson = new DslJson();
- testObject = new TestObject();
- testObject.field1 = "hello";
- testObject.field2 = "world";
- typeLiteral = TypeLiteral.create(TestObject.class);
- jsonWriter = new JsonWriter();
- }
-
- @Benchmark
- public void jsoniter() throws IOException {
- baos.reset();
- stream.reset(baos);
- stream.writeVal(typeLiteral, testObject);
- stream.flush();
- }
-
-// @Benchmark
- public void jsoniter_easy_mode(Blackhole bh) throws IOException {
- bh.consume(JsonStream.serialize(testObject));
- }
-
-// @Benchmark
- public void jackson() throws IOException {
- baos.reset();
- objectMapper.writeValue(baos, testObject);
- }
-
-// @Benchmark
- public void dsljson() throws IOException {
- baos.reset();
- jsonWriter.reset();
- ExternalSerialization.serialize(testObject, jsonWriter, false);
- jsonWriter.toStream(baos);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/PrivateFieldBinding.java b/demo/src/test/java/com/jsoniter/demo/PrivateFieldBinding.java
deleted file mode 100644
index 6c22cb87..00000000
--- a/demo/src/test/java/com/jsoniter/demo/PrivateFieldBinding.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package com.jsoniter.demo;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.ReflectionDecoderFactory;
-import com.jsoniter.annotation.JacksonAnnotationSupport;
-import com.jsoniter.spi.EmptyExtension;
-import com.jsoniter.spi.JsoniterSpi;
-import com.jsoniter.spi.ParameterizedTypeImpl;
-import com.jsoniter.spi.TypeLiteral;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.IOException;
-import java.lang.reflect.Type;
-import java.util.LinkedList;
-import java.util.List;
-
-@State(Scope.Thread)
-public class PrivateFieldBinding {
-
- private TypeLiteral typeLiteral;
- private ObjectMapper jackson;
- private byte[] input;
- private TypeReference typeRef;
- private String inputStr;
-
- public static class TestObject {
- @JsonProperty
- private int field1;
- @JsonProperty
- private int field2;
-
- @Override
- public String toString() {
- return "TestObject1{" +
- "field1=" + field1 +
- ", field2=" + field2 +
- '}';
- }
- }
-
-
- private JsonIterator iter;
-
- @Setup(Level.Trial)
- public void benchSetup() {
- inputStr = "{'field1':100,'field2':101}";
- input = inputStr.replace('\'', '"').getBytes();
- iter = JsonIterator.parse(input);
- typeLiteral = new TypeLiteral() {
- };
- typeRef = new TypeReference() {
- };
- JacksonAnnotationSupport.enable();
- JsoniterSpi.registerTypeDecoder(TestObject.class, ReflectionDecoderFactory.create(TestObject.class));
- jackson = new ObjectMapper();
- jackson.registerModule(new AfterburnerModule());
- }
-
- @Test
- public void test() throws IOException {
- JsoniterSpi.registerExtension(new EmptyExtension() {
- @Override
- public Type chooseImplementation(Type type) {
- if (ParameterizedTypeImpl.isSameClass(type, List.class)) {
- return ParameterizedTypeImpl.useImpl(type, LinkedList.class);
- } else {
- return type;
- }
- }
- });
- benchSetup();
- System.out.println(withJsoniter());
- System.out.println(withJackson());
- }
-
- @Benchmark
- public void withJsoniter(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJackson(Blackhole bh) throws IOException {
- bh.consume(withJackson());
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "PrivateFieldBinding.*",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- private TestObject withJsoniter() throws IOException {
- iter.reset(input);
- return iter.read(typeLiteral);
- }
-
- private TestObject withJackson() throws IOException {
- return jackson.readValue(input, typeRef);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/SetterBinding.java b/demo/src/test/java/com/jsoniter/demo/SetterBinding.java
deleted file mode 100644
index a695bee6..00000000
--- a/demo/src/test/java/com/jsoniter/demo/SetterBinding.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package com.jsoniter.demo;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
-import com.jsoniter.DecodingMode;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.ReflectionDecoderFactory;
-import com.jsoniter.annotation.JacksonAnnotationSupport;
-import com.jsoniter.spi.JsoniterSpi;
-import com.jsoniter.spi.TypeLiteral;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.IOException;
-
-@State(Scope.Thread)
-public class SetterBinding {
-
- private TypeLiteral typeLiteral;
- private ObjectMapper jackson;
- private byte[] input;
- private TypeReference typeRef;
- private String inputStr;
-
- public static class TestObject {
- private int field1;
- private int field2;
-
- public void setField1(int field1) {
- this.field1 = field1;
- }
-
- public void setField2(int field2) {
- this.field2 = field2;
- }
-
-// @JsonWrapper
-// public void initialize(
-// @JsonProperty("field1") int field1,
-// @JsonProperty("field2") int field2) {
-// this.field1 = field1;
-// this.field2 = field2;
-// }
- }
-
-
- private JsonIterator iter;
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- inputStr = "{'field1':100,'field2':101}".replace('\'', '"');
- input = inputStr.getBytes();
- iter = JsonIterator.parse(input);
- typeLiteral = new TypeLiteral() {
- };
- typeRef = new TypeReference() {
- };
- JacksonAnnotationSupport.enable();
- jackson = new ObjectMapper();
- jackson.registerModule(new AfterburnerModule());
- if (params != null) {
- if (params.getBenchmark().contains("withJsoniterStrictMode")) {
- JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY);
- }
- if (params.getBenchmark().contains("withJsoniterReflection")) {
- JsoniterSpi.registerTypeDecoder(ConstructorBinding.TestObject.class, ReflectionDecoderFactory.create(TestObject.class));
- }
- }
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- JsoniterSpi.registerTypeDecoder(ConstructorBinding.TestObject.class, ReflectionDecoderFactory.create(TestObject.class));
- System.out.println(withJsoniter());
- System.out.println(withJackson());
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "ConstructorBinding",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Benchmark
- public void withJsoniterHashMode(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJsoniterStrictMode(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJsoniterReflection(Blackhole bh) throws IOException {
- bh.consume(withJsoniter());
- }
-
- @Benchmark
- public void withJackson(Blackhole bh) throws IOException {
- bh.consume(withJackson());
- }
-
- private ConstructorBinding.TestObject withJsoniter() throws IOException {
- iter.reset(input);
- return iter.read(typeLiteral);
- }
-
- private ConstructorBinding.TestObject withJackson() throws IOException {
- return jackson.readValue(input, typeRef);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/SimpleObjectBinding.java b/demo/src/test/java/com/jsoniter/demo/SimpleObjectBinding.java
deleted file mode 100644
index c7408061..00000000
--- a/demo/src/test/java/com/jsoniter/demo/SimpleObjectBinding.java
+++ /dev/null
@@ -1,229 +0,0 @@
-package com.jsoniter.demo;
-
-import com.alibaba.fastjson.parser.DefaultJSONParser;
-import com.dslplatform.json.CompiledJson;
-import com.dslplatform.json.DslJson;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
-import com.jsoniter.DecodingMode;
-import com.jsoniter.JsonIterator;
-import com.jsoniter.ReflectionDecoderFactory;
-import com.jsoniter.spi.JsoniterSpi;
-import com.jsoniter.spi.TypeLiteral;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.infra.Blackhole;
-
-import java.io.IOException;
-
-@State(Scope.Thread)
-public class SimpleObjectBinding {
-
- private TypeLiteral typeLiteral;
- private ObjectMapper jackson;
- private byte[] input;
- private TypeReference typeRef;
- private DslJson dslJson;
- private Class clazz;
- private String inputStr;
- private TestObject testObject;
-
- @CompiledJson
- public static class TestObject {
- public int field1;
- public int field2;
-
- @Override
- public String toString() {
- return "TestObject1{" +
- "field1=" + field1 +
- ", field2=" + field2 +
- '}';
- }
- }
-
-
- private JsonIterator iter;
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- inputStr = "{'field1':100,'field2':101}".replace('\'', '"');
- input = inputStr.getBytes();
- iter = JsonIterator.parse(input);
- typeLiteral = new TypeLiteral() {
- };
- typeRef = new TypeReference() {
- };
- clazz = TestObject.class;
- jackson = new ObjectMapper();
- dslJson = new DslJson();
- testObject = new TestObject();
- if (params != null) {
- if (params.getBenchmark().contains("withReflection")) {
- JsoniterSpi.registerTypeDecoder(TestObject.class, ReflectionDecoderFactory.create(TestObject.class));
- }
- if (params.getBenchmark().contains("withBindApiStrictMode")) {
- JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY);
- }
- if (params.getBenchmark().contains("withJacksonAfterburner")) {
- jackson.registerModule(new AfterburnerModule());
- }
- }
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- JsoniterSpi.registerTypeDecoder(TestObject.class, ReflectionDecoderFactory.create(TestObject.class));
- System.out.println(withIterator());
- System.out.println(withIteratorIfElse());
- System.out.println(withIteratorIntern());
- System.out.println(withBindApi());
- System.out.println(withExistingObject());
- System.out.println(withJackson());
- System.out.println(withDsljson());
- System.out.println(withFastjson());
- }
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "SimpleObjectBinding.*",
- "-i", "5",
- "-wi", "5",
- "-f", "1"
- });
- }
-
-// @Benchmark
- public void withIterator(Blackhole bh) throws IOException {
- bh.consume(withIterator());
- }
-
-// @Benchmark
- public void withIteratorIfElse(Blackhole bh) throws IOException {
- bh.consume(withIteratorIfElse());
- }
-
-// @Benchmark
- public void withIteratorIntern(Blackhole bh) throws IOException {
- bh.consume(withIteratorIntern());
- }
-
- @Benchmark
- public void withoutExistingObject(Blackhole bh) throws IOException {
- bh.consume(withBindApi());
- }
-
- @Benchmark
- public void withBindApiStrictMode(Blackhole bh) throws IOException {
- bh.consume(withBindApi());
- }
-
- @Benchmark
- public void withReflection(Blackhole bh) throws IOException {
- bh.consume(withBindApi());
- }
-
- @Benchmark
- public void withExistingObject(Blackhole bh) throws IOException {
- bh.consume(withExistingObject());
- }
-
- @Benchmark
- public void withJacksonAfterburner(Blackhole bh) throws IOException {
- bh.consume(withJackson());
- }
-
- @Benchmark
- public void withJacksonNoAfterburner(Blackhole bh) throws IOException {
- bh.consume(withJackson());
- }
-
- @Benchmark
- public void withDsljson(Blackhole bh) throws IOException {
- bh.consume(withDsljson());
- }
-
- @Benchmark
- public void withFastjson(Blackhole bh) throws IOException {
- bh.consume(withFastjson());
- }
-
- private TestObject withIterator() throws IOException {
- iter.reset(input);
- TestObject obj = new TestObject();
- for (String field = iter.readObject(); field != null; field = iter.readObject()) {
- switch (field) {
- case "field1":
- obj.field1 = iter.readInt();
- continue;
- case "field2":
- obj.field2 = iter.readInt();
- continue;
- default:
- iter.skip();
- }
- }
- return obj;
- }
-
- private TestObject withIteratorIfElse() throws IOException {
- iter.reset(input);
- TestObject obj = new TestObject();
- for (String field = iter.readObject(); field != null; field = iter.readObject()) {
- if (field.equals("field1")) {
- obj.field1 = iter.readInt();
- continue;
- }
- if (field.equals("field2")) {
- obj.field2 = iter.readInt();
- continue;
- }
- iter.skip();
- }
- return obj;
- }
-
- private TestObject withIteratorIntern() throws IOException {
- iter.reset(input);
- TestObject obj = new TestObject();
- for (String field = iter.readObject(); field != null; field = iter.readObject()) {
- field = field.intern();
- if (field == "field1") {
- obj.field1 = iter.readInt();
- continue;
- }
- if (field == "field2") {
- obj.field2 = iter.readInt();
- continue;
- }
- iter.skip();
- }
- return obj;
- }
-
- private TestObject withBindApi() throws IOException {
- iter.reset(input);
- return iter.read(typeLiteral);
- }
-
- private TestObject withExistingObject() throws IOException {
- iter.reset(input);
- return iter.read(typeLiteral, testObject);
- }
-
- private TestObject withJackson() throws IOException {
- return jackson.readValue(input, typeRef);
- }
-
- private TestObject withDsljson() throws IOException {
- return (TestObject) dslJson.deserialize(clazz, input, input.length);
- }
-
- private TestObject withFastjson() {
- return new DefaultJSONParser(inputStr).parseObject(TestObject.class);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/StringOutput.java b/demo/src/test/java/com/jsoniter/demo/StringOutput.java
deleted file mode 100644
index fa47f09a..00000000
--- a/demo/src/test/java/com/jsoniter/demo/StringOutput.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.jsoniter.demo;
-
-
-import com.dslplatform.json.DslJson;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.jsoniter.output.JsonStream;
-import org.junit.Test;
-import org.openjdk.jmh.Main;
-import org.openjdk.jmh.annotations.*;
-import org.openjdk.jmh.infra.BenchmarkParams;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-@State(Scope.Thread)
-public class StringOutput {
-
- private ByteArrayOutputStream baos;
- private ObjectMapper objectMapper;
- private JsonStream stream;
- private byte[] buffer;
- private DslJson dslJson;
-
- public static void main(String[] args) throws Exception {
- Main.main(new String[]{
- "StringOutput",
- "-i", "5",
- "-wi", "5",
- "-f", "1",
- });
- }
-
- @Test
- public void test() throws IOException {
- benchSetup(null);
- jsoniter();
- System.out.println(baos.toString());
- jackson();
- System.out.println(baos.toString());
- dsljson();
- System.out.println(baos.toString());
- }
-
- @Setup(Level.Trial)
- public void benchSetup(BenchmarkParams params) {
- baos = new ByteArrayOutputStream(1024 * 64);
- objectMapper = new ObjectMapper();
- objectMapper.getFactory().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
- stream = new JsonStream(baos, 4096);
- buffer = new byte[4096];
- dslJson = new DslJson();
- }
-
- @Benchmark
- public void jsoniter() throws IOException {
- baos.reset();
- stream.reset(baos);
- stream.writeVal("hello world ~~ hello 中文 ~~~");
- stream.flush();
- }
-
- @Benchmark
- public void jackson() throws IOException {
- baos.reset();
- objectMapper.writeValue(baos, "hello world ~~ hello 中文 ~~~");
- }
-
- @Benchmark
- public void dsljson() throws IOException {
- baos.reset();
- dslJson.serialize("hello world ~~ hello 中文 ~~~", baos);
- }
-}
diff --git a/demo/src/test/java/com/jsoniter/demo/WrapperUnwrapper.java b/demo/src/test/java/com/jsoniter/demo/WrapperUnwrapper.java
deleted file mode 100644
index 5fafd580..00000000
--- a/demo/src/test/java/com/jsoniter/demo/WrapperUnwrapper.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.jsoniter.demo;
-
-import com.jsoniter.JsonIterator;
-import com.jsoniter.annotation.*;
-import com.jsoniter.output.JsonStream;
-import org.junit.Test;
-
-import java.io.IOException;
-
-public class WrapperUnwrapper {
-
- public static class Name {
- private final String firstName;
- private final String lastName;
-
- public Name(String firstName, String lastName) {
- this.firstName = firstName;
- this.lastName = lastName;
- }
-
- public String getFirstName() {
- return firstName;
- }
-
- public String getLastName() {
- return lastName;
- }
- }
-
-public static class User {
- private Name name;
- public int score;
-
- @JsonIgnore
- public Name getName() {
- return name;
- }
-
- @JsonUnwrapper
- public void writeName(JsonStream stream) throws IOException {
- stream.writeObjectField("firstName");
- stream.writeVal(name.getFirstName());
- stream.writeMore();
- stream.writeObjectField("lastName");
- stream.writeVal(name.getLastName());
- }
-
- @JsonWrapper
- public void setName(@JsonProperty("firstName") String firstName, @JsonProperty("lastName") String lastName) {
- System.out.println(firstName);
- name = new Name(firstName, lastName);
- }
-}
-
- @Test
- public void test() {
- JsoniterAnnotationSupport.enable();
- String input = "{'firstName': 'tao', 'lastName': 'wen', 'score': 100}".replace('\'', '\"');
- System.out.println(input);
- User user = JsonIterator.deserialize(input, User.class);
- System.out.println(user.getName().getFirstName());
- System.out.println(JsonStream.serialize(user));
-
- System.out.println(JsonStream.serialize(new int[]{1,2,3}));
- }
-}
diff --git a/demo/src/test/java/json/ExternalSerialization.java b/demo/src/test/java/json/ExternalSerialization.java
deleted file mode 100644
index 6727675f..00000000
--- a/demo/src/test/java/json/ExternalSerialization.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
-* Created by DSL Platform
-* v1.7.6214.30238
-*/
-
-package json;
-
-
-
-public class ExternalSerialization implements com.dslplatform.json.Configuration {
-
-
- @SuppressWarnings("unchecked")
- public void configure(final com.dslplatform.json.DslJson json) {
- setup(json);
- }
-
- @SuppressWarnings("unchecked")
- public static void setup(final com.dslplatform.json.DslJson json) {
-
-
- json.registerReader(com.jsoniter.demo.ObjectOutput.TestObject.class, JSON_READER_struct0);
- json.registerWriter(com.jsoniter.demo.ObjectOutput.TestObject.class, new com.dslplatform.json.JsonWriter.WriteObject() {
- @Override
- public void write(com.dslplatform.json.JsonWriter writer, com.jsoniter.demo.ObjectOutput.TestObject value) {
- serialize(value, writer, json.omitDefaults);
- }
- });
-
- json.registerReader(com.jsoniter.demo.SimpleObjectBinding.TestObject.class, JSON_READER_struct1);
- json.registerWriter(com.jsoniter.demo.SimpleObjectBinding.TestObject.class, new com.dslplatform.json.JsonWriter.WriteObject() {
- @Override
- public void write(com.dslplatform.json.JsonWriter writer, com.jsoniter.demo.SimpleObjectBinding.TestObject value) {
- serialize(value, writer, json.omitDefaults);
- }
- });
- }
-
- public static void serialize(final com.jsoniter.demo.ObjectOutput.TestObject self, final com.dslplatform.json.JsonWriter sw, final boolean minimal) {
- sw.writeByte(com.dslplatform.json.JsonWriter.OBJECT_START);
- if (minimal) {
- __serializeJsonObjectMinimal(self, sw, false);
- } else {
- __serializeJsonObjectFull(self, sw, false);
- }
- sw.writeByte(com.dslplatform.json.JsonWriter.OBJECT_END);
- }
-
- static void __serializeJsonObjectMinimal(final com.jsoniter.demo.ObjectOutput.TestObject self, com.dslplatform.json.JsonWriter sw, boolean hasWrittenProperty) {
-
-
- if (self.field1 != null) {
- hasWrittenProperty = true;
- sw.writeAscii("\"field1\":", 9);
- sw.writeString(self.field1);
- }
-
- if (self.field2 != null) {
- if(hasWrittenProperty) sw.writeByte(com.dslplatform.json.JsonWriter.COMMA);
- hasWrittenProperty = true;
- sw.writeAscii("\"field2\":", 9);
- sw.writeString(self.field2);
- }
- }
-
- static void __serializeJsonObjectFull(final com.jsoniter.demo.ObjectOutput.TestObject self, com.dslplatform.json.JsonWriter sw, boolean hasWrittenProperty) {
-
-
-
- if (self.field1 != null) {
- sw.writeAscii("\"field1\":", 9);
- sw.writeString(self.field1);
- } else {
- sw.writeAscii("\"field1\":null", 13);
- }
-
-
- if (self.field2 != null) {
- sw.writeAscii(",\"field2\":", 10);
- sw.writeString(self.field2);
- } else {
- sw.writeAscii(",\"field2\":null", 14);
- }
- }
-
- public static final com.dslplatform.json.JsonReader.ReadObject JSON_READER_struct0 = new com.dslplatform.json.JsonReader.ReadObject() {
- @SuppressWarnings("unchecked")
- @Override
- public com.jsoniter.demo.ObjectOutput.TestObject read(final com.dslplatform.json.JsonReader reader) throws java.io.IOException {
- if(reader.last() != '{') {
- throw new java.io.IOException("Expecting \'{\' at position " + reader.positionInStream() + ". Found " + (char)reader.last());
- }
- reader.getNextToken();
- final com.jsoniter.demo.ObjectOutput.TestObject instance = new com.jsoniter.demo.ObjectOutput.TestObject();
- deserialize(instance, reader);
- return instance;
- }
- };
-
- @SuppressWarnings("unchecked")
- static com.jsoniter.demo.ObjectOutput.TestObject deserializestruct0(final com.dslplatform.json.JsonReader reader) throws java.io.IOException {
- final com.jsoniter.demo.ObjectOutput.TestObject instance = new com.jsoniter.demo.ObjectOutput.TestObject();
- deserialize(instance, reader);
- return instance;
- }
-
- @SuppressWarnings("unchecked")
- static void deserialize(final com.jsoniter.demo.ObjectOutput.TestObject instance, final com.dslplatform.json.JsonReader reader) throws java.io.IOException {
-
- String _field1_ = null;
- String _field2_ = null;
- byte nextToken = reader.last();
- if(nextToken != '}') {
- int nameHash = reader.fillName();
- nextToken = reader.getNextToken();
- if(nextToken == 'n') {
- if (reader.wasNull()) {
- nextToken = reader.getNextToken();
- } else {
- throw new java.io.IOException("Expecting 'u' (as null) at position " + reader.positionInStream() + ". Found " + (char)nextToken);
- }
- } else {
- switch(nameHash) {
-
- case 1212206434:
- _field1_ = com.dslplatform.json.StringConverter.deserialize(reader);
- nextToken = reader.getNextToken();
- break;
- case 1195428815:
- _field2_ = com.dslplatform.json.StringConverter.deserialize(reader);
- nextToken = reader.getNextToken();
- break;
- default:
- nextToken = reader.skip();
- break;
- }
- }
- while (nextToken == ',') {
- nextToken = reader.getNextToken();
- nameHash = reader.fillName();
- nextToken = reader.getNextToken();
- if(nextToken == 'n') {
- if (reader.wasNull()) {
- nextToken = reader.getNextToken();
- continue;
- } else {
- throw new java.io.IOException("Expecting 'u' (as null) at position " + reader.positionInStream() + ". Found " + (char)nextToken);
- }
- }
- switch(nameHash) {
-
- case 1212206434:
- _field1_ = com.dslplatform.json.StringConverter.deserialize(reader);
- nextToken = reader.getNextToken();
- break;
- case 1195428815:
- _field2_ = com.dslplatform.json.StringConverter.deserialize(reader);
- nextToken = reader.getNextToken();
- break;
- default:
- nextToken = reader.skip();
- break;
- }
- }
- if (nextToken != '}') {
- throw new java.io.IOException("Expecting '}' at position " + reader.positionInStream() + ". Found " + (char)nextToken);
- }
- }
-
- instance.field1 = _field1_;
- instance.field2 = _field2_;
- }
-
- public static void serialize(final com.jsoniter.demo.SimpleObjectBinding.TestObject self, final com.dslplatform.json.JsonWriter sw, final boolean minimal) {
- sw.writeByte(com.dslplatform.json.JsonWriter.OBJECT_START);
- if (minimal) {
- __serializeJsonObjectMinimal(self, sw, false);
- } else {
- __serializeJsonObjectFull(self, sw, false);
- }
- sw.writeByte(com.dslplatform.json.JsonWriter.OBJECT_END);
- }
-
- static void __serializeJsonObjectMinimal(final com.jsoniter.demo.SimpleObjectBinding.TestObject self, com.dslplatform.json.JsonWriter sw, boolean hasWrittenProperty) {
-
-
- if (self.field1 != 0) {
- hasWrittenProperty = true;
- sw.writeAscii("\"field1\":", 9);
- com.dslplatform.json.NumberConverter.serialize(self.field1, sw);
- }
-
- if (self.field2 != 0) {
- if(hasWrittenProperty) sw.writeByte(com.dslplatform.json.JsonWriter.COMMA);
- hasWrittenProperty = true;
- sw.writeAscii("\"field2\":", 9);
- com.dslplatform.json.NumberConverter.serialize(self.field2, sw);
- }
- }
-
- static void __serializeJsonObjectFull(final com.jsoniter.demo.SimpleObjectBinding.TestObject self, com.dslplatform.json.JsonWriter sw, boolean hasWrittenProperty) {
-
-
-
- sw.writeAscii("\"field1\":", 9);
- com.dslplatform.json.NumberConverter.serialize(self.field1, sw);
-
-
- sw.writeAscii(",\"field2\":", 10);
- com.dslplatform.json.NumberConverter.serialize(self.field2, sw);
- }
-
- public static final com.dslplatform.json.JsonReader.ReadObject JSON_READER_struct1 = new com.dslplatform.json.JsonReader.ReadObject() {
- @SuppressWarnings("unchecked")
- @Override
- public com.jsoniter.demo.SimpleObjectBinding.TestObject read(final com.dslplatform.json.JsonReader reader) throws java.io.IOException {
- if(reader.last() != '{') {
- throw new java.io.IOException("Expecting \'{\' at position " + reader.positionInStream() + ". Found " + (char)reader.last());
- }
- reader.getNextToken();
- final com.jsoniter.demo.SimpleObjectBinding.TestObject instance = new com.jsoniter.demo.SimpleObjectBinding.TestObject();
- deserialize(instance, reader);
- return instance;
- }
- };
-
- @SuppressWarnings("unchecked")
- static com.jsoniter.demo.SimpleObjectBinding.TestObject deserializestruct1(final com.dslplatform.json.JsonReader reader) throws java.io.IOException {
- final com.jsoniter.demo.SimpleObjectBinding.TestObject instance = new com.jsoniter.demo.SimpleObjectBinding.TestObject();
- deserialize(instance, reader);
- return instance;
- }
-
- @SuppressWarnings("unchecked")
- static void deserialize(final com.jsoniter.demo.SimpleObjectBinding.TestObject instance, final com.dslplatform.json.JsonReader reader) throws java.io.IOException {
-
- int _field1_ = 0;
- int _field2_ = 0;
- byte nextToken = reader.last();
- if(nextToken != '}') {
- int nameHash = reader.fillName();
- nextToken = reader.getNextToken();
- if(nextToken == 'n') {
- if (reader.wasNull()) {
- nextToken = reader.getNextToken();
- } else {
- throw new java.io.IOException("Expecting 'u' (as null) at position " + reader.positionInStream() + ". Found " + (char)nextToken);
- }
- } else {
- switch(nameHash) {
-
- case 1212206434:
- _field1_ = com.dslplatform.json.NumberConverter.deserializeInt(reader);
- nextToken = reader.getNextToken();
- break;
- case 1195428815:
- _field2_ = com.dslplatform.json.NumberConverter.deserializeInt(reader);
- nextToken = reader.getNextToken();
- break;
- default:
- nextToken = reader.skip();
- break;
- }
- }
- while (nextToken == ',') {
- nextToken = reader.getNextToken();
- nameHash = reader.fillName();
- nextToken = reader.getNextToken();
- if(nextToken == 'n') {
- if (reader.wasNull()) {
- nextToken = reader.getNextToken();
- continue;
- } else {
- throw new java.io.IOException("Expecting 'u' (as null) at position " + reader.positionInStream() + ". Found " + (char)nextToken);
- }
- }
- switch(nameHash) {
-
- case 1212206434:
- _field1_ = com.dslplatform.json.NumberConverter.deserializeInt(reader);
- nextToken = reader.getNextToken();
- break;
- case 1195428815:
- _field2_ = com.dslplatform.json.NumberConverter.deserializeInt(reader);
- nextToken = reader.getNextToken();
- break;
- default:
- nextToken = reader.skip();
- break;
- }
- }
- if (nextToken != '}') {
- throw new java.io.IOException("Expecting '}' at position " + reader.positionInStream() + ". Found " + (char)nextToken);
- }
- }
-
- instance.field1 = _field1_;
- instance.field2 = _field2_;
- }
-}
diff --git a/demo/src/test/resources/large.json b/demo/src/test/resources/large.json
deleted file mode 100644
index 065d8c8b..00000000
--- a/demo/src/test/resources/large.json
+++ /dev/null
@@ -1,227 +0,0 @@
-[
- {
- "friends": [
- {
- "id": 0,
- "name": "Briana Gardner"
- },
- {
- "id": 1,
- "name": "Ratliff Byrd"
- },
- {
- "id": 2,
- "name": "Marks Bell"
- }
- ],
- "_id": "58659f97246aa21396247468",
- "index": 0,
- "guid": "73c7235f-121e-4871-89c2-97c986d2dc6c",
- "isActive": true,
- "balance": "$1,246.00",
- "picture": "http://placehold.it/32x32",
- "age": 35,
- "eyeColor": "blue",
- "name": "Winters Sharp",
- "gender": "male",
- "company": "POLARIA",
- "email": "winterssharp@polaria.com",
- "phone": "+1 (878) 493-3899",
- "address": "449 Troutman Street, Rockingham, Puerto Rico, 8953",
- "about": "Adipisicing eiusmod est amet pariatur velit laborum ea anim enim deserunt. Velit et do duis reprehenderit do Lorem do. Ipsum fugiat id commodo aliqua nulla. Laborum voluptate officia eiusmod dolor laborum labore nisi velit laboris aliqua ut labore adipisicing. Cupidatat tempor aute commodo fugiat nostrud nulla voluptate culpa anim do adipisicing sunt et.\r\n",
- "registered": "2016-05-13T03:23:52 -08:00",
- "latitude": -86.869083,
- "longitude": -42.636359,
- "tags": [
- "pariatur",
- "dolor",
- "exercitation",
- "non",
- "irure",
- "sint",
- "cupidatat"
- ],
- "greeting": "Hello, Winters Sharp! You have 7 unread messages.",
- "favoriteFruit": "banana"
- },
- {
- "friends": [
- {
- "id": 0,
- "name": "Patti Bird"
- },
- {
- "id": 1,
- "name": "Vinson Golden"
- },
- {
- "id": 2,
- "name": "Jessie Gallegos"
- }
- ],
- "_id": "58659f97fda793c4f321294b",
- "index": 1,
- "guid": "a1d948c6-a635-40d1-a4cc-75d8c54d90b7",
- "isActive": true,
- "balance": "$2,954.82",
- "picture": "http://placehold.it/32x32",
- "age": 23,
- "eyeColor": "green",
- "name": "Ann Reilly",
- "gender": "female",
- "company": "ACCUPHARM",
- "email": "annreilly@accupharm.com",
- "phone": "+1 (879) 560-2394",
- "address": "906 Johnson Avenue, Bagtown, Georgia, 6009",
- "about": "Commodo dolor in magna occaecat nostrud cillum tempor magna exercitation aliqua. Lorem qui voluptate ut nisi irure laborum. Quis sunt sint eu quis mollit aliquip sunt nostrud deserunt ipsum dolore exercitation esse tempor. Dolore tempor magna fugiat officia culpa qui minim deserunt incididunt duis. Occaecat consectetur commodo et fugiat tempor enim laborum. Amet pariatur occaecat est pariatur ut.\r\n",
- "registered": "2015-10-06T10:11:21 -08:00",
- "latitude": 28.347215,
- "longitude": 119.881559,
- "tags": [
- "excepteur",
- "sunt",
- "adipisicing",
- "excepteur",
- "qui",
- "quis",
- "veniam"
- ],
- "greeting": "Hello, Ann Reilly! You have 8 unread messages.",
- "favoriteFruit": "banana"
- },
- {
- "friends": [
- {
- "id": 0,
- "name": "Nichols Wooten"
- },
- {
- "id": 1,
- "name": "Catalina Welch"
- },
- {
- "id": 2,
- "name": "Stevens Vazquez"
- }
- ],
- "_id": "58659f97a722760b7fb775bd",
- "index": 2,
- "guid": "f75031dc-f80f-4144-888d-9fb78c50c218",
- "isActive": false,
- "balance": "$3,578.66",
- "picture": "http://placehold.it/32x32",
- "age": 32,
- "eyeColor": "brown",
- "name": "Doreen Rowland",
- "gender": "female",
- "company": "COMTRACT",
- "email": "doreenrowland@comtract.com",
- "phone": "+1 (874) 444-2553",
- "address": "554 Voorhies Avenue, Odessa, Northern Mariana Islands, 4525",
- "about": "Qui consequat adipisicing ea non aute eu magna veniam nostrud. Aliqua sint id eiusmod sint eu irure occaecat sit Lorem eu. Aliquip nostrud pariatur cupidatat minim pariatur ullamco dolore deserunt fugiat. Culpa laboris anim exercitation quis eu. Ea aute officia irure laborum sint amet quis labore aliqua ex exercitation culpa quis. Proident amet aliquip esse eiusmod sit. Veniam aute ea non cillum eiusmod deserunt magna commodo incididunt elit sint ut.\r\n",
- "registered": "2014-12-31T05:58:46 -08:00",
- "latitude": -79.177177,
- "longitude": -106.982224,
- "tags": [
- "voluptate",
- "deserunt",
- "adipisicing",
- "proident",
- "exercitation",
- "proident",
- "est"
- ],
- "greeting": "Hello, Doreen Rowland! You have 4 unread messages.",
- "favoriteFruit": "apple"
- },
- {
- "friends": [
- {
- "id": 0,
- "name": "Bryant Beard"
- },
- {
- "id": 1,
- "name": "Josephine Glover"
- },
- {
- "id": 2,
- "name": "Socorro Koch"
- }
- ],
- "_id": "58659f9779501e6cd634508b",
- "index": 3,
- "guid": "9e52955b-2365-4136-b972-39e72b01b0b6",
- "isActive": false,
- "balance": "$1,612.42",
- "picture": "http://placehold.it/32x32",
- "age": 30,
- "eyeColor": "green",
- "name": "Leach Peck",
- "gender": "male",
- "company": "SLOFAST",
- "email": "leachpeck@slofast.com",
- "phone": "+1 (889) 464-3474",
- "address": "659 Carroll Street, Balm, New Jersey, 883",
- "about": "Nisi officia esse eiusmod exercitation. Fugiat sunt labore Lorem nisi aliqua deserunt fugiat nisi nisi tempor duis. Consequat nostrud anim nisi commodo eiusmod sit ex do occaecat dolor consequat incididunt Lorem duis. Est dolore elit adipisicing laborum nostrud sit labore aliqua officia quis amet ut laboris. Quis anim incididunt exercitation tempor consectetur sunt cupidatat exercitation veniam. Consequat Lorem in eu duis ut incididunt excepteur id qui anim id consectetur commodo.\r\n",
- "registered": "2014-03-04T02:41:37 -08:00",
- "latitude": 55.648581,
- "longitude": 152.684842,
- "tags": [
- "laborum",
- "ex",
- "reprehenderit",
- "eu",
- "esse",
- "officia",
- "Lorem"
- ],
- "greeting": "Hello, Leach Peck! You have 2 unread messages.",
- "favoriteFruit": "banana"
- },
- {
- "_id": "58659f976f045f9c69c53efb",
- "index": 4,
- "guid": "3cbaef3d-25ab-48d0-8807-1974f6aad336",
- "isActive": true,
- "balance": "$1,854.63",
- "picture": "http://placehold.it/32x32",
- "age": 26,
- "eyeColor": "brown",
- "name": "Briggs Larson",
- "gender": "male",
- "company": "ZOXY",
- "email": "briggslarson@zoxy.com",
- "phone": "+1 (807) 588-3350",
- "address": "994 Nichols Avenue, Allamuchy, Guam, 3824",
- "about": "Sunt velit ullamco consequat velit ad nisi in sint qui qui ut eiusmod eu. Et ut aliqua mollit cupidatat et proident tempor do est enim exercitation amet aliquip. Non exercitation proident do duis non ullamco do esse dolore in occaecat. Magna ea labore aliqua laborum ad amet est incididunt et quis cillum nulla. Adipisicing veniam nisi esse officia dolor labore. Proident fugiat consequat ullamco fugiat. Est et adipisicing eiusmod excepteur deserunt pariatur aute commodo dolore occaecat veniam dolore.\r\n",
- "registered": "2014-07-21T03:28:39 -08:00",
- "latitude": -59.741245,
- "longitude": -9.657004,
- "friends": [
- {
- "id": 0,
- "name": "Herminia Mcknight"
- },
- {
- "id": 1,
- "name": "Leann Harding"
- },
- {
- "id": 2,
- "name": "Marisol Sykes"
- }
- ],
- "tags": [
- "ea",
- "velit",
- "sunt",
- "fugiat",
- "do",
- "Lorem",
- "nostrud"
- ],
- "greeting": "Hello, Briggs Larson! You have 3 unread messages.",
- "favoriteFruit": "apple"
- }
-]
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index cbdc6a02..4840503f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.jsoniter
- 0.9.6-SNAPSHOT
+ 0.9.24-SNAPSHOT
jsoniter
json iterator
jsoniter (json-iterator) is fast and flexible JSON parser available in Java and Go
@@ -46,15 +46,40 @@
org.javassist
javassist
- 3.21.0-GA
+ 3.22.0-GA
true
com.fasterxml.jackson.core
jackson-annotations
- 2.8.5
+ 2.9.5
true
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.9.5
+ true
+
+
+ com.google.code.gson
+ gson
+ 2.8.3
+ true
+
+
+
+ org.openjdk.jmh
+ jmh-core
+ 1.20
+ test
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ 1.20
+ test
+
@@ -79,10 +104,22 @@
+
+ org.codehaus.mojo
+ cobertura-maven-plugin
+ 2.7
+
+
+ html
+ xml
+
+
+
+
org.apache.maven.plugins
maven-compiler-plugin
- 3.6.0
+ 3.7.0
1.6
1.6
@@ -92,7 +129,7 @@
org.apache.maven.plugins
maven-source-plugin
- 2.2.1
+ 3.0.1
attach-sources
@@ -105,7 +142,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 2.9.1
+ 3.0.0
attach-javadocs
@@ -121,7 +158,7 @@
org.apache.maven.plugins
maven-gpg-plugin
- 1.5
+ 1.6
sign-artifacts
@@ -135,7 +172,7 @@
org.sonatype.plugins
nexus-staging-maven-plugin
- 1.6.7
+ 1.6.8
true
ossrh
@@ -154,6 +191,24 @@
deploy
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.21.0
+
+ methods
+ 1
+ false
+ 4
+
+ com.jsoniter.suite.StreamingTests
+ com.jsoniter.suite.NonStreamingTests
+ com.jsoniter.suite.NonStreamingTests4Hash
+ com.jsoniter.suite.NonStreamingTests4Strict
+ com.jsoniter.suite.ExtraTests
+
+
+
@@ -163,4 +218,4 @@
https://oss.sonatype.org/content/repositories/snapshots
-
\ No newline at end of file
+
diff --git a/src/main/java/com/jsoniter/Codegen.java b/src/main/java/com/jsoniter/Codegen.java
index 50002157..7cf7318d 100644
--- a/src/main/java/com/jsoniter/Codegen.java
+++ b/src/main/java/com/jsoniter/Codegen.java
@@ -8,24 +8,14 @@
import java.io.OutputStreamWriter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
import java.util.*;
class Codegen {
// only read/write when generating code with synchronized protection
private final static Set generatedClassNames = new HashSet();
- static boolean isDoingStaticCodegen = false;
- static DecodingMode mode = DecodingMode.REFLECTION_MODE;
- static {
- String envMode = System.getenv("JSONITER_DECODING_MODE");
- if (envMode != null) {
- mode = DecodingMode.valueOf(envMode);
- }
- }
-
- public static void setMode(DecodingMode mode) {
- Codegen.mode = mode;
- }
+ static CodegenAccess.StaticCodegenTarget isDoingStaticCodegen = null;
static Decoder getDecoder(String cacheKey, Type type) {
Decoder decoder = JsoniterSpi.getDecoder(cacheKey);
@@ -52,52 +42,77 @@ private synchronized static Decoder gen(String cacheKey, Type type) {
return decoder;
}
}
- Type[] typeArgs = new Type[0];
- Class clazz;
- if (type instanceof ParameterizedType) {
- ParameterizedType pType = (ParameterizedType) type;
- clazz = (Class) pType.getRawType();
- typeArgs = pType.getActualTypeArguments();
- } else {
- clazz = (Class) type;
- }
- if (mode == DecodingMode.REFLECTION_MODE) {
- decoder = ReflectionDecoderFactory.create(clazz, typeArgs);
- JsoniterSpi.addNewDecoder(cacheKey, decoder);
+ ClassInfo classInfo = new ClassInfo(type);
+ decoder = CodegenImplNative.NATIVE_DECODERS.get(classInfo.clazz);
+ if (decoder != null) {
return decoder;
}
+ addPlaceholderDecoderToSupportRecursiveStructure(cacheKey);
try {
- decoder = (Decoder) Class.forName(cacheKey).newInstance();
- JsoniterSpi.addNewDecoder(cacheKey, decoder);
- return decoder;
- } catch (Exception e) {
- if (mode == DecodingMode.STATIC_MODE) {
- throw new JsonException("static gen should provide the decoder we need, but failed to create the decoder", e);
+ Config currentConfig = JsoniterSpi.getCurrentConfig();
+ DecodingMode mode = currentConfig.decodingMode();
+ if (mode == DecodingMode.REFLECTION_MODE) {
+ decoder = ReflectionDecoderFactory.create(classInfo);
+ return decoder;
}
- }
- String source = genSource(clazz, typeArgs);
- source = "public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { "
- + source + "}";
- if ("true".equals(System.getenv("JSONITER_DEBUG"))) {
- System.out.println(">>> " + cacheKey);
- System.out.println(source);
- }
- try {
- generatedClassNames.add(cacheKey);
- if (isDoingStaticCodegen) {
- staticGen(cacheKey, source);
- } else {
- decoder = DynamicCodegen.gen(cacheKey, source);
+ if (isDoingStaticCodegen == null) {
+ try {
+ decoder = (Decoder) Class.forName(cacheKey).newInstance();
+ return decoder;
+ } catch (Exception e) {
+ if (mode == DecodingMode.STATIC_MODE) {
+ throw new JsonException("static gen should provide the decoder we need, but failed to create the decoder", e);
+ }
+ }
+ }
+ String source = genSource(mode, classInfo);
+ source = "public static java.lang.Object decode_(com.jsoniter.JsonIterator iter) throws java.io.IOException { "
+ + source + "}";
+ if ("true".equals(System.getenv("JSONITER_DEBUG"))) {
+ System.out.println(">>> " + cacheKey);
+ System.out.println(source);
+ }
+ try {
+ generatedClassNames.add(cacheKey);
+ if (isDoingStaticCodegen == null) {
+ decoder = DynamicCodegen.gen(cacheKey, source);
+ } else {
+ staticGen(cacheKey, source);
+ }
+ return decoder;
+ } catch (Exception e) {
+ String msg = "failed to generate decoder for: " + classInfo + " with " + Arrays.toString(classInfo.typeArgs) + ", exception: " + e;
+ msg = msg + "\n" + source;
+ throw new JsonException(msg, e);
}
+ } finally {
JsoniterSpi.addNewDecoder(cacheKey, decoder);
- return decoder;
- } catch (Exception e) {
- System.err.println("failed to generate decoder for: " + type + " with " + Arrays.toString(typeArgs) + ", exception: " + e);
- System.err.println(source);
- throw new JsonException(e);
}
}
+ private static void addPlaceholderDecoderToSupportRecursiveStructure(final String cacheKey) {
+ JsoniterSpi.addNewDecoder(cacheKey, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ Decoder decoder = JsoniterSpi.getDecoder(cacheKey);
+ if (this == decoder) {
+ for(int i = 0; (i < 30) && (this == decoder); i++) {
+ decoder = JsoniterSpi.getDecoder(cacheKey);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ throw new JsonException(e);
+ }
+ }
+ if (this == decoder) {
+ throw new JsonException("internal error: placeholder is not replaced with real decoder");
+ }
+ }
+ return decoder.decode(iter);
+ }
+ });
+ }
+
public static boolean canStaticAccess(String cacheKey) {
return generatedClassNames.contains(cacheKey);
}
@@ -109,6 +124,8 @@ private static Type chooseImpl(Type type) {
ParameterizedType pType = (ParameterizedType) type;
clazz = (Class) pType.getRawType();
typeArgs = pType.getActualTypeArguments();
+ } else if (type instanceof WildcardType) {
+ return Object.class;
} else {
clazz = (Class) type;
}
@@ -129,7 +146,7 @@ private static Type chooseImpl(Type type) {
} else if (clazz == Set.class) {
clazz = implClazz == null ? HashSet.class : implClazz;
}
- return new ParameterizedTypeImpl(new Type[]{compType}, null, clazz);
+ return GenericsHelper.createParameterizedType(new Type[]{compType}, null, clazz);
}
if (Map.class.isAssignableFrom(clazz)) {
Type keyType = String.class;
@@ -144,19 +161,20 @@ private static Type chooseImpl(Type type) {
"can not bind to generic collection without argument types, " +
"try syntax like TypeLiteral>{}");
}
- if (keyType != String.class) {
- throw new IllegalArgumentException("map key must be String");
- }
if (clazz == Map.class) {
clazz = implClazz == null ? HashMap.class : implClazz;
}
- return new ParameterizedTypeImpl(new Type[]{keyType, valueType}, null, clazz);
+ if (keyType == Object.class) {
+ keyType = String.class;
+ }
+ MapKeyDecoders.registerOrGetExisting(keyType);
+ return GenericsHelper.createParameterizedType(new Type[]{keyType, valueType}, null, clazz);
}
if (implClazz != null) {
if (typeArgs.length == 0) {
return implClazz;
} else {
- return new ParameterizedTypeImpl(typeArgs, null, implClazz);
+ return GenericsHelper.createParameterizedType(typeArgs, null, implClazz);
}
}
return type;
@@ -165,7 +183,7 @@ private static Type chooseImpl(Type type) {
private static void staticGen(String cacheKey, String source) throws IOException {
createDir(cacheKey);
String fileName = cacheKey.replace('.', '/') + ".java";
- FileOutputStream fileOutputStream = new FileOutputStream(fileName);
+ FileOutputStream fileOutputStream = new FileOutputStream(new File(isDoingStaticCodegen.outputDir, fileName));
try {
OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream);
try {
@@ -192,7 +210,7 @@ private static void staticGen(String cacheKey, OutputStreamWriter writer, String
private static void createDir(String cacheKey) {
String[] parts = cacheKey.split("\\.");
- File parent = new File(".");
+ File parent = new File(isDoingStaticCodegen.outputDir);
for (int i = 0; i < parts.length - 1; i++) {
String part = parts[i];
File current = new File(parent, part);
@@ -201,31 +219,28 @@ private static void createDir(String cacheKey) {
}
}
- private static String genSource(Class clazz, Type[] typeArgs) {
- if (CodegenImplNative.NATIVE_READS.containsKey(clazz.getName())) {
- return CodegenImplNative.genNative(clazz.getName());
+ private static String genSource(DecodingMode mode, ClassInfo classInfo) {
+ if (classInfo.clazz.isArray()) {
+ return CodegenImplArray.genArray(classInfo);
}
- if (clazz.isArray()) {
- return CodegenImplArray.genArray(clazz);
- }
- if (Map.class.isAssignableFrom(clazz)) {
- return CodegenImplMap.genMap(clazz, typeArgs);
+ if (Map.class.isAssignableFrom(classInfo.clazz)) {
+ return CodegenImplMap.genMap(classInfo);
}
- if (Collection.class.isAssignableFrom(clazz)) {
- return CodegenImplArray.genCollection(clazz, typeArgs);
+ if (Collection.class.isAssignableFrom(classInfo.clazz)) {
+ return CodegenImplArray.genCollection(classInfo);
}
- if (clazz.isEnum()) {
- return CodegenImplEnum.genEnum(clazz);
+ if (classInfo.clazz.isEnum()) {
+ return CodegenImplEnum.genEnum(classInfo);
}
- ClassDescriptor desc = JsoniterSpi.getDecodingClassDescriptor(clazz, false);
- if (shouldUseStrictMode(desc)) {
- return CodegenImplObject.genObjectUsingStrict(clazz, desc);
+ ClassDescriptor desc = ClassDescriptor.getDecodingClassDescriptor(classInfo, false);
+ if (shouldUseStrictMode(mode, desc)) {
+ return CodegenImplObjectStrict.genObjectUsingStrict(desc);
} else {
- return CodegenImplObject.genObjectUsingHash(clazz, desc);
+ return CodegenImplObjectHash.genObjectUsingHash(desc);
}
}
- private static boolean shouldUseStrictMode(ClassDescriptor desc) {
+ private static boolean shouldUseStrictMode(DecodingMode mode, ClassDescriptor desc) {
if (mode == DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY) {
return true;
}
@@ -240,14 +255,24 @@ private static boolean shouldUseStrictMode(ClassDescriptor desc) {
// only slice support unknown field tracking
return true;
}
- if (allBindings.isEmpty()) {
+ if (!desc.keyValueTypeWrappers.isEmpty()) {
+ return true;
+ }
+ boolean hasBinding = false;
+ for (Binding allBinding : allBindings) {
+ if (allBinding.fromNames.length > 0) {
+ hasBinding = true;
+ }
+ }
+ if (!hasBinding) {
+ // empty object can only be handled by strict mode
return true;
}
return false;
}
- public static void staticGenDecoders(TypeLiteral[] typeLiterals) {
- isDoingStaticCodegen = true;
+ public static void staticGenDecoders(TypeLiteral[] typeLiterals, CodegenAccess.StaticCodegenTarget staticCodegenTarget) {
+ isDoingStaticCodegen = staticCodegenTarget;
for (TypeLiteral typeLiteral : typeLiterals) {
gen(typeLiteral.getDecoderCacheKey(), typeLiteral.getType());
}
diff --git a/src/main/java/com/jsoniter/CodegenAccess.java b/src/main/java/com/jsoniter/CodegenAccess.java
index 40586a91..bc4f3cb7 100644
--- a/src/main/java/com/jsoniter/CodegenAccess.java
+++ b/src/main/java/com/jsoniter/CodegenAccess.java
@@ -1,8 +1,6 @@
package com.jsoniter;
-import com.jsoniter.spi.Decoder;
-import com.jsoniter.spi.JsoniterSpi;
-import com.jsoniter.spi.TypeLiteral;
+import com.jsoniter.spi.*;
import java.io.IOException;
import java.util.Collection;
@@ -36,46 +34,29 @@ public static void setExistingObject(JsonIterator iter, Object obj) {
iter.existingObject = obj;
}
- public static byte nextToken(JsonIterator iter) throws IOException {
- return IterImpl.nextToken(iter);
+ public final static boolean nextTokenIsComma(final JsonIterator iter) throws IOException {
+ byte c = readByte(iter);
+ if (c == ',') {
+ return true;
+ }
+ return nextTokenIsCommaSlowPath(iter, c);
}
- public static final T read(JsonIterator iter, TypeLiteral typeLiteral) throws IOException {
- TypeLiteral.NativeType nativeType = typeLiteral.getNativeType();
- if (nativeType != null) {
- switch (nativeType) {
- case FLOAT:
- return (T) Float.valueOf(iter.readFloat());
- case DOUBLE:
- return (T) Double.valueOf(iter.readDouble());
- case BOOLEAN:
- return (T) Boolean.valueOf(iter.readBoolean());
- case BYTE:
- return (T) Byte.valueOf((byte) iter.readShort());
- case SHORT:
- return (T) Short.valueOf(iter.readShort());
- case INT:
- return (T) Integer.valueOf(iter.readInt());
- case CHAR:
- return (T) Character.valueOf((char) iter.readInt());
- case LONG:
- return (T) Long.valueOf(iter.readLong());
- case BIG_DECIMAL:
- return (T) iter.readBigDecimal();
- case BIG_INTEGER:
- return (T) iter.readBigInteger();
- case STRING:
- return (T) iter.readString();
- case OBJECT:
- return (T) iter.read();
- case ANY:
- return (T) iter.readAny();
- default:
- throw new JsonException("unsupported native type: " + nativeType);
- }
- } else {
- return (T) Codegen.getDecoder(typeLiteral.getDecoderCacheKey(), typeLiteral.getType()).decode(iter);
+ private static boolean nextTokenIsCommaSlowPath(JsonIterator iter, byte c) throws IOException {
+ switch (c) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ break;
+ default:
+ return false;
}
+ return nextToken(iter) == ',';
+ }
+
+ public static byte nextToken(JsonIterator iter) throws IOException {
+ return IterImpl.nextToken(iter);
}
public static final boolean readBoolean(String cacheKey, JsonIterator iter) throws IOException {
@@ -160,6 +141,15 @@ public static final Slice readSlice(JsonIterator iter) throws IOException {
return IterImpl.readSlice(iter);
}
+ public static final Object readMapKey(String cacheKey, JsonIterator iter) throws IOException {
+ Decoder mapKeyDecoder = JsoniterSpi.getMapKeyDecoder(cacheKey);
+ Object key = mapKeyDecoder.decode(iter);
+ if (IterImpl.nextToken(iter) != ':') {
+ throw iter.reportError("readMapKey", "expect :");
+ }
+ return key;
+ }
+
final static boolean skipWhitespacesWithoutLoadMore(JsonIterator iter) throws IOException {
for (int i = iter.head; i < iter.tail; i++) {
byte c = iter.buf[i];
@@ -176,8 +166,8 @@ final static boolean skipWhitespacesWithoutLoadMore(JsonIterator iter) throws IO
return true;
}
- public static void staticGenDecoders(TypeLiteral[] typeLiterals) {
- Codegen.staticGenDecoders(typeLiterals);
+ public static void staticGenDecoders(TypeLiteral[] typeLiterals, StaticCodegenTarget staticCodegenTarget) {
+ Codegen.staticGenDecoders(typeLiterals, staticCodegenTarget);
}
public static int head(JsonIterator iter) {
@@ -187,4 +177,24 @@ public static int head(JsonIterator iter) {
public static void unreadByte(JsonIterator iter) throws IOException {
iter.unreadByte();
}
+
+ public static byte readByte(JsonIterator iter) throws IOException {
+ return IterImpl.readByte(iter);
+ }
+
+ public static int calcHash(String str) {
+ return CodegenImplObjectHash.calcHash(str);
+ }
+
+ public static void skipFixedBytes(JsonIterator iter, int n) throws IOException {
+ IterImpl.skipFixedBytes(iter, n);
+ }
+
+ public static class StaticCodegenTarget {
+ public String outputDir;
+
+ public StaticCodegenTarget(String outputDir) {
+ this.outputDir = outputDir;
+ }
+ }
}
diff --git a/src/main/java/com/jsoniter/CodegenImplArray.java b/src/main/java/com/jsoniter/CodegenImplArray.java
index d24fb47f..a80ef7af 100644
--- a/src/main/java/com/jsoniter/CodegenImplArray.java
+++ b/src/main/java/com/jsoniter/CodegenImplArray.java
@@ -1,5 +1,7 @@
package com.jsoniter;
+import com.jsoniter.spi.ClassInfo;
+
import java.lang.reflect.Type;
import java.util.*;
@@ -11,37 +13,56 @@ class CodegenImplArray {
add(Vector.class);
}};
- public static String genArray(Class clazz) {
- Class compType = clazz.getComponentType();
+ public static String genArray(ClassInfo classInfo) {
+ Class compType = classInfo.clazz.getComponentType();
if (compType.isArray()) {
- throw new IllegalArgumentException("nested array not supported: " + clazz.getCanonicalName());
+ throw new IllegalArgumentException("nested array not supported: " + classInfo.clazz.getCanonicalName());
}
StringBuilder lines = new StringBuilder();
append(lines, "com.jsoniter.CodegenAccess.resetExistingObject(iter);");
- append(lines, "if (iter.readNull()) { return null; }");
- append(lines, "if (!com.jsoniter.CodegenAccess.readArrayStart(iter)) {");
+ append(lines, "byte nextToken = com.jsoniter.CodegenAccess.readByte(iter);");
+ append(lines, "if (nextToken != '[') {");
+ append(lines, "if (nextToken == 'n') {");
+ append(lines, "com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);");
+ append(lines, "com.jsoniter.CodegenAccess.resetExistingObject(iter); return null;");
+ append(lines, "} else {");
+ append(lines, "nextToken = com.jsoniter.CodegenAccess.nextToken(iter);");
+ append(lines, "if (nextToken == 'n') {");
+ append(lines, "com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);");
+ append(lines, "com.jsoniter.CodegenAccess.resetExistingObject(iter); return null;");
+ append(lines, "}");
+ append(lines, "}");
+ append(lines, "}");
+ append(lines, "nextToken = com.jsoniter.CodegenAccess.nextToken(iter);");
+ append(lines, "if (nextToken == ']') {");
append(lines, "return new {{comp}}[0];");
append(lines, "}");
+ append(lines, "com.jsoniter.CodegenAccess.unreadByte(iter);");
append(lines, "{{comp}} a1 = {{op}};");
- append(lines, "if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {");
+ append(lines, "if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {");
append(lines, "return new {{comp}}[]{ a1 };");
append(lines, "}");
append(lines, "{{comp}} a2 = {{op}};");
- append(lines, "if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {");
+ append(lines, "if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {");
append(lines, "return new {{comp}}[]{ a1, a2 };");
append(lines, "}");
append(lines, "{{comp}} a3 = {{op}};");
- append(lines, "if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {");
+ append(lines, "if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {");
append(lines, "return new {{comp}}[]{ a1, a2, a3 };");
append(lines, "}");
append(lines, "{{comp}} a4 = ({{comp}}) {{op}};");
- append(lines, "{{comp}}[] arr = new {{comp}}[8];");
+ append(lines, "if (!com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {");
+ append(lines, "return new {{comp}}[]{ a1, a2, a3, a4 };");
+ append(lines, "}");
+ append(lines, "{{comp}} a5 = ({{comp}}) {{op}};");
+ append(lines, "{{comp}}[] arr = new {{comp}}[10];");
append(lines, "arr[0] = a1;");
append(lines, "arr[1] = a2;");
append(lines, "arr[2] = a3;");
append(lines, "arr[3] = a4;");
- append(lines, "int i = 4;");
- append(lines, "while (com.jsoniter.CodegenAccess.nextToken(iter) == ',') {");
+ append(lines, "arr[4] = a5;");
+ append(lines, "int i = 5;");
+ append(lines, "while (com.jsoniter.CodegenAccess.nextTokenIsComma(iter)) {");
append(lines, "if (i == arr.length) {");
append(lines, "{{comp}}[] newArr = new {{comp}}[arr.length * 2];");
append(lines, "System.arraycopy(arr, 0, newArr, 0, arr.length);");
@@ -58,18 +79,18 @@ public static String genArray(Class clazz) {
"{{op}}", CodegenImplNative.genReadOp(compType));
}
- public static String genCollection(Class clazz, Type[] typeArgs) {
- if (WITH_CAPACITY_COLLECTION_CLASSES.contains(clazz)) {
- return CodegenImplArray.genCollectionWithCapacity(clazz, typeArgs[0]);
+ public static String genCollection(ClassInfo classInfo) {
+ if (WITH_CAPACITY_COLLECTION_CLASSES.contains(classInfo.clazz)) {
+ return CodegenImplArray.genCollectionWithCapacity(classInfo.clazz, classInfo.typeArgs[0]);
} else {
- return CodegenImplArray.genCollectionWithoutCapacity(clazz, typeArgs[0]);
+ return CodegenImplArray.genCollectionWithoutCapacity(classInfo.clazz, classInfo.typeArgs[0]);
}
}
private static String genCollectionWithCapacity(Class clazz, Type compType) {
StringBuilder lines = new StringBuilder();
append(lines, "{{clazz}} col = ({{clazz}})com.jsoniter.CodegenAccess.resetExistingObject(iter);");
- append(lines, "if (iter.readNull()) { return null; }");
+ append(lines, "if (iter.readNull()) { com.jsoniter.CodegenAccess.resetExistingObject(iter); return null; }");
append(lines, "if (!com.jsoniter.CodegenAccess.readArrayStart(iter)) {");
append(lines, "return col == null ? new {{clazz}}(0): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
append(lines, "}");
@@ -103,7 +124,6 @@ private static String genCollectionWithCapacity(Class clazz, Type compType) {
append(lines, "while (com.jsoniter.CodegenAccess.nextToken(iter) == ',') {");
append(lines, "obj.add({{op}});");
append(lines, "}");
-// append(lines, "if (c != ']') { com.jsoniter.CodegenAccess.reportIncompleteArray(iter); }");
append(lines, "return obj;");
return lines.toString().replace(
"{{clazz}}", clazz.getName()).replace(
@@ -112,7 +132,7 @@ private static String genCollectionWithCapacity(Class clazz, Type compType) {
private static String genCollectionWithoutCapacity(Class clazz, Type compType) {
StringBuilder lines = new StringBuilder();
- append(lines, "if (iter.readNull()) { return null; }");
+ append(lines, "if (iter.readNull()) { com.jsoniter.CodegenAccess.resetExistingObject(iter); return null; }");
append(lines, "{{clazz}} col = ({{clazz}})com.jsoniter.CodegenAccess.resetExistingObject(iter);");
append(lines, "if (!com.jsoniter.CodegenAccess.readArrayStart(iter)) {");
append(lines, "return col == null ? new {{clazz}}(): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
diff --git a/src/main/java/com/jsoniter/CodegenImplEnum.java b/src/main/java/com/jsoniter/CodegenImplEnum.java
index 7303ea2a..8e9d084f 100644
--- a/src/main/java/com/jsoniter/CodegenImplEnum.java
+++ b/src/main/java/com/jsoniter/CodegenImplEnum.java
@@ -1,16 +1,18 @@
package com.jsoniter;
+import com.jsoniter.spi.ClassInfo;
+
import java.util.*;
-public class CodegenImplEnum {
- public static String genEnum(Class clazz) {
+class CodegenImplEnum {
+ public static String genEnum(ClassInfo classInfo) {
StringBuilder lines = new StringBuilder();
append(lines, "if (iter.readNull()) { return null; }");
- append(lines, "com.jsoniter.Slice field = com.jsoniter.CodegenAccess.readSlice(iter);");
+ append(lines, "com.jsoniter.spi.Slice field = com.jsoniter.CodegenAccess.readSlice(iter);");
append(lines, "switch (field.len()) {");
- append(lines, renderTriTree(buildTriTree(Arrays.asList(clazz.getEnumConstants()))));
+ append(lines, renderTriTree(buildTriTree(Arrays.asList(classInfo.clazz.getEnumConstants()))));
append(lines, "}"); // end of switch
- append(lines, String.format("throw iter.reportError(\"decode enum\", field + \" is not valid enum for %s\");", clazz.getName()));
+ append(lines, String.format("throw iter.reportError(\"decode enum\", field + \" is not valid enum for %s\");", classInfo.clazz.getName()));
return lines.toString();
}
diff --git a/src/main/java/com/jsoniter/CodegenImplMap.java b/src/main/java/com/jsoniter/CodegenImplMap.java
index a7670570..4f64a77a 100644
--- a/src/main/java/com/jsoniter/CodegenImplMap.java
+++ b/src/main/java/com/jsoniter/CodegenImplMap.java
@@ -1,13 +1,15 @@
package com.jsoniter;
+import com.jsoniter.spi.ClassInfo;
+import com.jsoniter.spi.TypeLiteral;
+
import java.lang.reflect.Type;
-import java.util.HashMap;
-import java.util.Map;
class CodegenImplMap {
- public static String genMap(Class clazz, Type[] typeArgs) {
- Type valueType = typeArgs[1];
+ public static String genMap(ClassInfo classInfo) {
+ Type keyType = classInfo.typeArgs[0];
+ Type valueType = classInfo.typeArgs[1];
StringBuilder lines = new StringBuilder();
append(lines, "{{clazz}} map = ({{clazz}})com.jsoniter.CodegenAccess.resetExistingObject(iter);");
append(lines, "if (iter.readNull()) { return null; }");
@@ -15,14 +17,19 @@ public static String genMap(Class clazz, Type[] typeArgs) {
append(lines, "if (!com.jsoniter.CodegenAccess.readObjectStart(iter)) {");
append(lines, "return map;");
append(lines, "}");
- append(lines, "String field = com.jsoniter.CodegenAccess.readObjectFieldAsString(iter);");
- append(lines, "map.put(field, {{op}});");
- append(lines, "while (com.jsoniter.CodegenAccess.nextToken(iter) == ',') {");
- append(lines, "field = com.jsoniter.CodegenAccess.readObjectFieldAsString(iter);");
- append(lines, "map.put(field, {{op}});");
- append(lines, "}");
+ append(lines, "do {");
+ if (keyType == String.class) {
+ append(lines, "java.lang.Object mapKey = com.jsoniter.CodegenAccess.readObjectFieldAsString(iter);");
+ } else {
+ append(lines, "java.lang.Object mapKey = com.jsoniter.CodegenAccess.readMapKey(\"" +
+ TypeLiteral.create(keyType).getDecoderCacheKey() +"\", iter);");
+ }
+ append(lines, "map.put(mapKey, {{op}});");
+ append(lines, "} while (com.jsoniter.CodegenAccess.nextToken(iter) == ',');");
append(lines, "return map;");
- return lines.toString().replace("{{clazz}}", clazz.getName()).replace("{{op}}", CodegenImplNative.genReadOp(valueType));
+ return lines.toString()
+ .replace("{{clazz}}", classInfo.clazz.getName())
+ .replace("{{op}}", CodegenImplNative.genReadOp(valueType));
}
private static void append(StringBuilder lines, String str) {
diff --git a/src/main/java/com/jsoniter/CodegenImplNative.java b/src/main/java/com/jsoniter/CodegenImplNative.java
index 5280480f..156ce8f2 100644
--- a/src/main/java/com/jsoniter/CodegenImplNative.java
+++ b/src/main/java/com/jsoniter/CodegenImplNative.java
@@ -1,10 +1,12 @@
package com.jsoniter;
import com.jsoniter.any.Any;
-import com.jsoniter.spi.TypeLiteral;
+import com.jsoniter.spi.*;
+import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
@@ -20,62 +22,152 @@ class CodegenImplNative {
put("int", "iter.readInt()");
put("char", "iter.readInt()");
put("long", "iter.readLong()");
- put(Float.class.getName(), "java.lang.Float.valueOf(iter.readFloat())");
- put(Double.class.getName(), "java.lang.Double.valueOf(iter.readDouble())");
- put(Boolean.class.getName(), "java.lang.Boolean.valueOf(iter.readBoolean())");
- put(Byte.class.getName(), "java.lang.Byte.valueOf((byte)iter.readShort())");
- put(Character.class.getName(), "java.lang.Character.valueOf((char)iter.readShort())");
- put(Short.class.getName(), "java.lang.Short.valueOf(iter.readShort())");
- put(Integer.class.getName(), "java.lang.Integer.valueOf(iter.readInt())");
- put(Long.class.getName(), "java.lang.Long.valueOf(iter.readLong())");
+ put(Float.class.getName(), "(iter.readNull() ? null : java.lang.Float.valueOf(iter.readFloat()))");
+ put(Double.class.getName(), "(iter.readNull() ? null : java.lang.Double.valueOf(iter.readDouble()))");
+ put(Boolean.class.getName(), "(iter.readNull() ? null : java.lang.Boolean.valueOf(iter.readBoolean()))");
+ put(Byte.class.getName(), "(iter.readNull() ? null : java.lang.Byte.valueOf((byte)iter.readShort()))");
+ put(Character.class.getName(), "(iter.readNull() ? null : java.lang.Character.valueOf((char)iter.readShort()))");
+ put(Short.class.getName(), "(iter.readNull() ? null : java.lang.Short.valueOf(iter.readShort()))");
+ put(Integer.class.getName(), "(iter.readNull() ? null : java.lang.Integer.valueOf(iter.readInt()))");
+ put(Long.class.getName(), "(iter.readNull() ? null : java.lang.Long.valueOf(iter.readLong()))");
put(BigDecimal.class.getName(), "iter.readBigDecimal()");
put(BigInteger.class.getName(), "iter.readBigInteger()");
put(String.class.getName(), "iter.readString()");
put(Object.class.getName(), "iter.read()");
put(Any.class.getName(), "iter.readAny()");
}};
-
- public static String genNative(String nativeReadKey) {
- if ("boolean".equals(nativeReadKey)) {
- nativeReadKey = Boolean.class.getName();
- } else if ("byte".equals(nativeReadKey)) {
- nativeReadKey = Byte.class.getName();
- } else if ("char".equals(nativeReadKey)) {
- nativeReadKey = Character.class.getName();
- } else if ("short".equals(nativeReadKey)) {
- nativeReadKey = Short.class.getName();
- } else if ("int".equals(nativeReadKey)) {
- nativeReadKey = Integer.class.getName();
- } else if ("long".equals(nativeReadKey)) {
- nativeReadKey = Long.class.getName();
- } else if ("float".equals(nativeReadKey)) {
- nativeReadKey = Float.class.getName();
- } else if ("double".equals(nativeReadKey)) {
- nativeReadKey = Double.class.getName();
- }
- String op = NATIVE_READS.get(nativeReadKey);
- if (op == null) {
- throw new JsonException("do not know how to read: " + nativeReadKey);
- }
- return "return " + op + ";";
- }
+ final static Map NATIVE_DECODERS = new HashMap() {{
+ put(float.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readFloat();
+ }
+ });
+ put(Float.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readNull() ? null : iter.readFloat();
+ }
+ });
+ put(double.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readDouble();
+ }
+ });
+ put(Double.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readNull() ? null : iter.readDouble();
+ }
+ });
+ put(boolean.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readBoolean();
+ }
+ });
+ put(Boolean.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readNull() ? null : iter.readBoolean();
+ }
+ });
+ put(byte.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return Byte.valueOf((byte) iter.readShort());
+ }
+ });
+ put(Byte.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readNull() ? null : (byte)iter.readShort();
+ }
+ });
+ put(short.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readShort();
+ }
+ });
+ put(Short.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readNull() ? null : iter.readShort();
+ }
+ });
+ put(int.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readInt();
+ }
+ });
+ put(Integer.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readNull() ? null : iter.readInt();
+ }
+ });
+ put(char.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return (char)iter.readInt();
+ }
+ });
+ put(Character.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readNull() ? null : (char)iter.readInt();
+ }
+ });
+ put(long.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readLong();
+ }
+ });
+ put(Long.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readNull() ? null : iter.readLong();
+ }
+ });
+ put(BigDecimal.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readBigDecimal();
+ }
+ });
+ put(BigInteger.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readBigInteger();
+ }
+ });
+ put(String.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readString();
+ }
+ });
+ put(Object.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.read();
+ }
+ });
+ put(Any.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readAny();
+ }
+ });
+ }};
public static String genReadOp(Type type) {
- if (type instanceof Class) {
- Class clazz = (Class) type;
- String nativeRead = NATIVE_READS.get(clazz.getCanonicalName());
- if (nativeRead != null) {
- return nativeRead;
- }
- }
String cacheKey = TypeLiteral.create(type).getDecoderCacheKey();
- Codegen.getDecoder(cacheKey, type);// set the decoder to cache
- if (Codegen.canStaticAccess(cacheKey)) {
- return String.format("%s.decode_(iter)", cacheKey);
- } else {
- // can not use static "decode_" method to access, go through codegen cache
- return String.format("com.jsoniter.CodegenAccess.read(\"%s\", iter)", cacheKey);
- }
+ return String.format("(%s)%s", getTypeName(type), genReadOp(cacheKey, type));
}
public static String getTypeName(Type fieldType) {
@@ -86,8 +178,95 @@ public static String getTypeName(Type fieldType) {
ParameterizedType pType = (ParameterizedType) fieldType;
Class clazz = (Class) pType.getRawType();
return clazz.getCanonicalName();
+ } else if (fieldType instanceof WildcardType) {
+ return Object.class.getCanonicalName();
} else {
throw new JsonException("unsupported type: " + fieldType);
}
}
+
+ static String genField(Binding field) {
+ String fieldCacheKey = field.decoderCacheKey();
+ Type fieldType = field.valueType;
+ return String.format("(%s)%s", getTypeName(fieldType), genReadOp(fieldCacheKey, fieldType));
+
+ }
+
+ private static String genReadOp(String cacheKey, Type valueType) {
+ // the field decoder might be registered directly
+ Decoder decoder = JsoniterSpi.getDecoder(cacheKey);
+ if (decoder == null) {
+ // if cache key is for field, and there is no field decoder specified
+ // update cache key for normal type
+ cacheKey = TypeLiteral.create(valueType).getDecoderCacheKey();
+ decoder = JsoniterSpi.getDecoder(cacheKey);
+ if (decoder == null) {
+ if (valueType instanceof Class) {
+ Class clazz = (Class) valueType;
+ String nativeRead = NATIVE_READS.get(clazz.getCanonicalName());
+ if (nativeRead != null) {
+ return nativeRead;
+ }
+ } else if (valueType instanceof WildcardType) {
+ return NATIVE_READS.get(Object.class.getCanonicalName());
+ }
+ Codegen.getDecoder(cacheKey, valueType);
+ if (Codegen.canStaticAccess(cacheKey)) {
+ return String.format("%s.decode_(iter)", cacheKey);
+ } else {
+ // can not use static "decode_" method to access, go through codegen cache
+ return String.format("com.jsoniter.CodegenAccess.read(\"%s\", iter)", cacheKey);
+ }
+ }
+ }
+ if (valueType == boolean.class) {
+ if (!(decoder instanceof Decoder.BooleanDecoder)) {
+ throw new JsonException("decoder for " + cacheKey + "must implement Decoder.BooleanDecoder");
+ }
+ return String.format("com.jsoniter.CodegenAccess.readBoolean(\"%s\", iter)", cacheKey);
+ }
+ if (valueType == byte.class) {
+ if (!(decoder instanceof Decoder.ShortDecoder)) {
+ throw new JsonException("decoder for " + cacheKey + "must implement Decoder.ShortDecoder");
+ }
+ return String.format("com.jsoniter.CodegenAccess.readShort(\"%s\", iter)", cacheKey);
+ }
+ if (valueType == short.class) {
+ if (!(decoder instanceof Decoder.ShortDecoder)) {
+ throw new JsonException("decoder for " + cacheKey + "must implement Decoder.ShortDecoder");
+ }
+ return String.format("com.jsoniter.CodegenAccess.readShort(\"%s\", iter)", cacheKey);
+ }
+ if (valueType == char.class) {
+ if (!(decoder instanceof Decoder.IntDecoder)) {
+ throw new JsonException("decoder for " + cacheKey + "must implement Decoder.IntDecoder");
+ }
+ return String.format("com.jsoniter.CodegenAccess.readInt(\"%s\", iter)", cacheKey);
+ }
+ if (valueType == int.class) {
+ if (!(decoder instanceof Decoder.IntDecoder)) {
+ throw new JsonException("decoder for " + cacheKey + "must implement Decoder.IntDecoder");
+ }
+ return String.format("com.jsoniter.CodegenAccess.readInt(\"%s\", iter)", cacheKey);
+ }
+ if (valueType == long.class) {
+ if (!(decoder instanceof Decoder.LongDecoder)) {
+ throw new JsonException("decoder for " + cacheKey + "must implement Decoder.LongDecoder");
+ }
+ return String.format("com.jsoniter.CodegenAccess.readLong(\"%s\", iter)", cacheKey);
+ }
+ if (valueType == float.class) {
+ if (!(decoder instanceof Decoder.FloatDecoder)) {
+ throw new JsonException("decoder for " + cacheKey + "must implement Decoder.FloatDecoder");
+ }
+ return String.format("com.jsoniter.CodegenAccess.readFloat(\"%s\", iter)", cacheKey);
+ }
+ if (valueType == double.class) {
+ if (!(decoder instanceof Decoder.DoubleDecoder)) {
+ throw new JsonException("decoder for " + cacheKey + "must implement Decoder.DoubleDecoder");
+ }
+ return String.format("com.jsoniter.CodegenAccess.readDouble(\"%s\", iter)", cacheKey);
+ }
+ return String.format("com.jsoniter.CodegenAccess.read(\"%s\", iter)", cacheKey);
+ }
}
diff --git a/src/main/java/com/jsoniter/CodegenImplObjectHash.java b/src/main/java/com/jsoniter/CodegenImplObjectHash.java
new file mode 100644
index 00000000..e0982ec0
--- /dev/null
+++ b/src/main/java/com/jsoniter/CodegenImplObjectHash.java
@@ -0,0 +1,188 @@
+package com.jsoniter;
+
+import com.jsoniter.spi.*;
+
+import java.util.*;
+
+class CodegenImplObjectHash {
+
+ // the implementation is from dsljson, it is the fastest although has the risk not matching field strictly
+ public static String genObjectUsingHash(ClassDescriptor desc) {
+ Class clazz = desc.clazz;
+ StringBuilder lines = new StringBuilder();
+ // === if null, return null
+ append(lines, "java.lang.Object existingObj = com.jsoniter.CodegenAccess.resetExistingObject(iter);");
+ append(lines, "byte nextToken = com.jsoniter.CodegenAccess.readByte(iter);");
+ append(lines, "if (nextToken != '{') {");
+ append(lines, "if (nextToken == 'n') {");
+ append(lines, "com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);");
+ append(lines, "return null;");
+ append(lines, "} else {");
+ append(lines, "nextToken = com.jsoniter.CodegenAccess.nextToken(iter);");
+ append(lines, "if (nextToken == 'n') {");
+ append(lines, "com.jsoniter.CodegenAccess.skipFixedBytes(iter, 3);");
+ append(lines, "return null;");
+ append(lines, "}");
+ append(lines, "} // end of if null");
+ append(lines, "} // end of if {");
+ // === if empty, return empty
+ // ctor requires binding
+ for (Binding parameter : desc.ctor.parameters) {
+ appendVarDef(lines, parameter);
+ }
+ append(lines, "nextToken = com.jsoniter.CodegenAccess.readByte(iter);");
+ append(lines, "if (nextToken != '\"') {");
+ append(lines, "if (nextToken == '}') {");
+ append(lines, "return {{newInst}};");
+ append(lines, "} else {");
+ append(lines, "nextToken = com.jsoniter.CodegenAccess.nextToken(iter);");
+ append(lines, "if (nextToken == '}') {");
+ append(lines, "return {{newInst}};");
+ append(lines, "} else {");
+ append(lines, "com.jsoniter.CodegenAccess.unreadByte(iter);");
+ append(lines, "}");
+ append(lines, "} // end of if end");
+ append(lines, "} else { com.jsoniter.CodegenAccess.unreadByte(iter); }// end of if not quote");
+ for (Binding field : desc.fields) {
+ if (field.fromNames.length == 0) {
+ continue;
+ }
+ appendVarDef(lines, field);
+ }
+ for (Binding setter : desc.setters) {
+ appendVarDef(lines, setter);
+ }
+ for (WrapperDescriptor setter : desc.bindingTypeWrappers) {
+ for (Binding param : setter.parameters) {
+ appendVarDef(lines, param);
+ }
+ }
+ // === bind fields
+ HashSet knownHashes = new HashSet();
+ HashMap bindings = new HashMap();
+ for (Binding binding : desc.allDecoderBindings()) {
+ for (String fromName : binding.fromNames) {
+ bindings.put(fromName, binding);
+ }
+ }
+ ArrayList fromNames = new ArrayList(bindings.keySet());
+ Collections.sort(fromNames, new Comparator() {
+ @Override
+ public int compare(String o1, String o2) {
+ int x = calcHash(o1);
+ int y = calcHash(o2);
+ return (x < y) ? -1 : ((x == y) ? 0 : 1);
+ }
+ });
+ // === bind more fields
+ append(lines, "do {");
+ append(lines, "switch (com.jsoniter.CodegenAccess.readObjectFieldAsHash(iter)) {");
+ for (String fromName : fromNames) {
+ int intHash = calcHash(fromName);
+ if (intHash == 0) {
+ // hash collision, 0 can not be used as sentinel
+ return CodegenImplObjectStrict.genObjectUsingStrict(desc);
+ }
+ if (knownHashes.contains(intHash)) {
+ // hash collision with other field can not be used as sentinel
+ return CodegenImplObjectStrict.genObjectUsingStrict(desc);
+ }
+ knownHashes.add(intHash);
+ append(lines, "case " + intHash + ": ");
+ appendBindingSet(lines, desc, bindings.get(fromName));
+ append(lines, "continue;");
+ }
+ append(lines, "}");
+ append(lines, "iter.skip();");
+ append(lines, "} while (com.jsoniter.CodegenAccess.nextTokenIsComma(iter));");
+ append(lines, CodegenImplNative.getTypeName(clazz) + " obj = {{newInst}};");
+ for (Binding field : desc.fields) {
+ if (field.fromNames.length == 0) {
+ continue;
+ }
+ append(lines, String.format("obj.%s = _%s_;", field.field.getName(), field.name));
+ }
+ for (Binding setter : desc.setters) {
+ append(lines, String.format("obj.%s(_%s_);", setter.method.getName(), setter.name));
+ }
+ appendWrappers(desc.bindingTypeWrappers, lines);
+ append(lines, "return obj;");
+ return lines.toString()
+ .replace("{{clazz}}", clazz.getCanonicalName())
+ .replace("{{newInst}}", genNewInstCode(clazz, desc.ctor));
+ }
+
+ public static int calcHash(String fromName) {
+ long hash = 0x811c9dc5;
+ for (byte b : fromName.getBytes()) {
+ hash ^= b;
+ hash *= 0x1000193;
+ }
+ return (int) hash;
+ }
+
+ private static void appendBindingSet(StringBuilder lines, ClassDescriptor desc, Binding binding) {
+ append(lines, String.format("_%s_ = %s;", binding.name, CodegenImplNative.genField(binding)));
+ }
+
+ static void appendWrappers(List wrappers, StringBuilder lines) {
+ for (WrapperDescriptor wrapper : wrappers) {
+ lines.append("obj.");
+ lines.append(wrapper.method.getName());
+ appendInvocation(lines, wrapper.parameters);
+ lines.append(";\n");
+ }
+ }
+
+ static void appendVarDef(StringBuilder lines, Binding parameter) {
+ String typeName = CodegenImplNative.getTypeName(parameter.valueType);
+ append(lines, String.format("%s _%s_ = %s;", typeName, parameter.name, CodegenImplObjectStrict.DEFAULT_VALUES.get(typeName)));
+ }
+
+ static String genNewInstCode(Class clazz, ConstructorDescriptor ctor) {
+ StringBuilder code = new StringBuilder();
+ if (ctor.parameters.isEmpty()) {
+ // nothing to bind, safe to reuse existing object
+ code.append("(existingObj == null ? ");
+ }
+ if (ctor.objectFactory != null) {
+ code.append(String.format("(%s)com.jsoniter.spi.JsoniterSpi.create(%s.class)",
+ clazz.getCanonicalName(), clazz.getCanonicalName()));
+ } else {
+ if (ctor.staticMethodName == null) {
+ code.append(String.format("new %s", clazz.getCanonicalName()));
+ } else {
+ code.append(String.format("%s.%s", clazz.getCanonicalName(), ctor.staticMethodName));
+ }
+ }
+ List params = ctor.parameters;
+ if (ctor.objectFactory == null) {
+ appendInvocation(code, params);
+ }
+ if (ctor.parameters.isEmpty()) {
+ // nothing to bind, safe to reuse existing obj
+ code.append(String.format(" : (%s)existingObj)", clazz.getCanonicalName()));
+ }
+ return code.toString();
+ }
+
+ private static void appendInvocation(StringBuilder code, List params) {
+ code.append("(");
+ boolean isFirst = true;
+ for (Binding ctorParam : params) {
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ code.append(",");
+ }
+ code.append(String.format("_%s_", ctorParam.name));
+ }
+ code.append(")");
+ }
+
+ static void append(StringBuilder lines, String str) {
+ lines.append(str);
+ lines.append("\n");
+ }
+
+}
diff --git a/src/main/java/com/jsoniter/CodegenImplObject.java b/src/main/java/com/jsoniter/CodegenImplObjectStrict.java
similarity index 53%
rename from src/main/java/com/jsoniter/CodegenImplObject.java
rename to src/main/java/com/jsoniter/CodegenImplObjectStrict.java
index 1cf5c995..ba87eaa2 100644
--- a/src/main/java/com/jsoniter/CodegenImplObject.java
+++ b/src/main/java/com/jsoniter/CodegenImplObjectStrict.java
@@ -2,11 +2,13 @@
import com.jsoniter.spi.*;
-import java.lang.reflect.Type;
+import java.lang.reflect.Method;
import java.util.*;
-import java.util.zip.CRC32;
-class CodegenImplObject {
+import static com.jsoniter.CodegenImplObjectHash.appendVarDef;
+import static com.jsoniter.CodegenImplObjectHash.appendWrappers;
+
+class CodegenImplObjectStrict {
final static Map DEFAULT_VALUES = new HashMap() {{
put("float", "0.0f");
@@ -19,7 +21,7 @@ class CodegenImplObject {
put("long", "0");
}};
- public static String genObjectUsingStrict(Class clazz, ClassDescriptor desc) {
+ public static String genObjectUsingStrict(ClassDescriptor desc) {
List allBindings = desc.allDecoderBindings();
int lastRequiredIdx = assignMaskForRequiredProperties(allBindings);
boolean hasRequiredBinding = lastRequiredIdx > 0;
@@ -38,7 +40,8 @@ public static String genObjectUsingStrict(Class clazz, ClassDescriptor desc) {
* 8. apply multi param wrappers
*/
// === if null, return null
- append(lines, "if (iter.readNull()) { com.jsoniter.CodegenAccess.resetExistingObject(iter); return null; }");
+ append(lines, "java.lang.Object existingObj = com.jsoniter.CodegenAccess.resetExistingObject(iter);");
+ append(lines, "if (iter.readNull()) { return null; }");
// === if input is empty obj, return empty obj
if (hasRequiredBinding) {
append(lines, "long tracker = 0;");
@@ -65,22 +68,25 @@ public static String genObjectUsingStrict(Class clazz, ClassDescriptor desc) {
}
append(lines, "}");
for (Binding field : desc.fields) {
+ if (field.fromNames.length == 0) {
+ continue;
+ }
appendVarDef(lines, field);
}
for (Binding setter : desc.setters) {
appendVarDef(lines, setter);
}
}
- for (WrapperDescriptor wrapper : desc.wrappers) {
+ for (WrapperDescriptor wrapper : desc.bindingTypeWrappers) {
for (Binding param : wrapper.parameters) {
appendVarDef(lines, param);
}
}
// === bind first field
- if (desc.onExtraProperties != null) {
+ if (desc.onExtraProperties != null || !desc.keyValueTypeWrappers.isEmpty()) {
append(lines, "java.util.Map extra = null;");
}
- append(lines, "com.jsoniter.Slice field = com.jsoniter.CodegenAccess.readObjectFieldAsSlice(iter);");
+ append(lines, "com.jsoniter.spi.Slice field = com.jsoniter.CodegenAccess.readObjectFieldAsSlice(iter);");
append(lines, "boolean once = true;");
append(lines, "while (once) {");
append(lines, "once = false;");
@@ -88,6 +94,9 @@ public static String genObjectUsingStrict(Class clazz, ClassDescriptor desc) {
if (desc.ctor.parameters.isEmpty()) {
// if not field or setter, the value will set to temp variable
for (Binding field : desc.fields) {
+ if (field.fromNames.length == 0) {
+ continue;
+ }
rendered = updateBindingSetOp(rendered, field);
}
for (Binding setter : desc.setters) {
@@ -119,25 +128,43 @@ public static String genObjectUsingStrict(Class clazz, ClassDescriptor desc) {
if (desc.onExtraProperties != null) {
appendSetExtraProperteis(lines, desc);
}
+ if (!desc.keyValueTypeWrappers.isEmpty()) {
+ appendSetExtraToKeyValueTypeWrappers(lines, desc);
+ }
if (!desc.ctor.parameters.isEmpty()) {
- append(lines, String.format("%s obj = {{newInst}};", CodegenImplNative.getTypeName(clazz)));
+ append(lines, String.format("%s obj = {{newInst}};", CodegenImplNative.getTypeName(desc.clazz)));
for (Binding field : desc.fields) {
+ if (field.fromNames.length == 0) {
+ continue;
+ }
append(lines, String.format("obj.%s = _%s_;", field.field.getName(), field.name));
}
for (Binding setter : desc.setters) {
append(lines, String.format("obj.%s(_%s_);", setter.method.getName(), setter.name));
}
}
- appendWrappers(desc.wrappers, lines);
+ appendWrappers(desc.bindingTypeWrappers, lines);
append(lines, "return obj;");
return lines.toString()
- .replace("{{clazz}}", clazz.getCanonicalName())
- .replace("{{newInst}}", genNewInstCode(clazz, desc.ctor));
+ .replace("{{clazz}}", desc.clazz.getCanonicalName())
+ .replace("{{newInst}}", CodegenImplObjectHash.genNewInstCode(desc.clazz, desc.ctor));
+ }
+
+ private static void appendSetExtraToKeyValueTypeWrappers(StringBuilder lines, ClassDescriptor desc) {
+ append(lines, "java.util.Iterator extraIter = extra.entrySet().iterator();");
+ append(lines, "while(extraIter.hasNext()) {");
+ for (Method wrapper : desc.keyValueTypeWrappers) {
+ append(lines, "java.util.Map.Entry entry = (java.util.Map.Entry)extraIter.next();");
+ append(lines, "String key = entry.getKey().toString();");
+ append(lines, "com.jsoniter.any.Any value = (com.jsoniter.any.Any)entry.getValue();");
+ append(lines, String.format("obj.%s(key, value.object());", wrapper.getName()));
+ }
+ append(lines, "}");
}
private static void appendSetExtraProperteis(StringBuilder lines, ClassDescriptor desc) {
Binding onExtraProperties = desc.onExtraProperties;
- if (ParameterizedTypeImpl.isSameClass(onExtraProperties.valueType, Map.class)) {
+ if (GenericsHelper.isSameClass(onExtraProperties.valueType, Map.class)) {
if (onExtraProperties.field != null) {
append(lines, String.format("obj.%s = extra;", onExtraProperties.field.getName()));
} else {
@@ -173,6 +200,9 @@ private static int assignMaskForRequiredProperties(List allBindings) {
}
private static String updateBindingSetOp(String rendered, Binding binding) {
+ if (binding.fromNames.length == 0) {
+ return rendered;
+ }
while (true) {
String marker = "_" + binding.name + "_";
int start = rendered.indexOf(marker);
@@ -217,7 +247,7 @@ private static void appendMissingRequiredProperties(StringBuilder lines, ClassDe
}
}
if (desc.onMissingProperties == null || !desc.ctor.parameters.isEmpty()) {
- append(lines, "throw new com.jsoniter.JsonException(\"missing required properties: \" + missingFields);");
+ append(lines, "throw new com.jsoniter.spi.JsonException(\"missing required properties: \" + missingFields);");
} else {
if (desc.onMissingProperties.field != null) {
append(lines, String.format("obj.%s = missingFields;", desc.onMissingProperties.field.getName()));
@@ -228,15 +258,15 @@ private static void appendMissingRequiredProperties(StringBuilder lines, ClassDe
}
private static void appendOnUnknownField(StringBuilder lines, ClassDescriptor desc) {
- if (desc.asExtraForUnknownProperties) {
- if (desc.onExtraProperties == null) {
- append(lines, "throw new com.jsoniter.JsonException('extra property: ' + field.toString());".replace('\'', '"'));
- } else {
+ if (desc.asExtraForUnknownProperties && desc.onExtraProperties == null) {
+ append(lines, "throw new com.jsoniter.spi.JsonException('extra property: ' + field.toString());".replace('\'', '"'));
+ } else {
+ if (desc.asExtraForUnknownProperties || !desc.keyValueTypeWrappers.isEmpty()) {
append(lines, "if (extra == null) { extra = new java.util.HashMap(); }");
append(lines, "extra.put(field.toString(), iter.readAny());");
+ } else {
+ append(lines, "iter.skip();");
}
- } else {
- append(lines, "iter.skip();");
}
}
@@ -292,13 +322,13 @@ private static void addFieldDispatch(
Binding field = (Binding) entry.getValue();
if (field.asExtraWhenPresent) {
append(lines, String.format(
- "throw new com.jsoniter.JsonException('extra property: %s');".replace('\'', '"'),
+ "throw new com.jsoniter.spi.JsonException('extra property: %s');".replace('\'', '"'),
field.name));
} else if (field.shouldSkip) {
append(lines, "iter.skip();");
append(lines, "continue;");
} else {
- append(lines, String.format("_%s_ = %s;", field.name, genField(field)));
+ append(lines, String.format("_%s_ = %s;", field.name, CodegenImplNative.genField(field)));
if (field.asMissingWhenNotPresent) {
append(lines, "tracker = tracker | " + field.mask + "L;");
}
@@ -326,127 +356,6 @@ private static void addFieldDispatch(
}
}
- // the implementation is from dsljson, it is the fastest although has the risk not matching field strictly
- public static String genObjectUsingHash(Class clazz, ClassDescriptor desc) {
- StringBuilder lines = new StringBuilder();
- // === if null, return null
- append(lines, "if (iter.readNull()) { com.jsoniter.CodegenAccess.resetExistingObject(iter); return null; }");
- // === if empty, return empty
- if (desc.ctor.parameters.isEmpty()) {
- // has default ctor
- append(lines, "{{clazz}} obj = {{newInst}};");
- append(lines, "if (!com.jsoniter.CodegenAccess.readObjectStart(iter)) { return obj; }");
- } else {
- // ctor requires binding
- for (Binding parameter : desc.ctor.parameters) {
- appendVarDef(lines, parameter);
- }
- append(lines, "if (!com.jsoniter.CodegenAccess.readObjectStart(iter)) { return {{newInst}}; }");
- for (Binding field : desc.fields) {
- appendVarDef(lines, field);
- }
- for (Binding setter : desc.setters) {
- appendVarDef(lines, setter);
- }
- }
- for (WrapperDescriptor setter : desc.wrappers) {
- for (Binding param : setter.parameters) {
- appendVarDef(lines, param);
- }
- }
- // === bind fields
- append(lines, "switch (com.jsoniter.CodegenAccess.readObjectFieldAsHash(iter)) {");
- HashSet knownHashes = new HashSet();
- List bindings = desc.allDecoderBindings();
- for (Binding field : bindings) {
- for (String fromName : field.fromNames) {
- long hash = 0x811c9dc5;
- for (byte b : fromName.getBytes()) {
- hash ^= b;
- hash *= 0x1000193;
- }
- int intHash = (int) hash;
- if (intHash == 0) {
- // hash collision, 0 can not be used as sentinel
- return genObjectUsingStrict(clazz, desc);
- }
- if (knownHashes.contains(intHash)) {
- // hash collision with other field can not be used as sentinel
- return genObjectUsingStrict(clazz, desc);
- }
- knownHashes.add(intHash);
- append(lines, "case " + intHash + ": ");
- appendBindingSet(lines, desc, field);
- append(lines, "break;");
- }
- }
- append(lines, "default:");
- append(lines, "iter.skip();");
- append(lines, "}");
- // === bind more fields
- append(lines, "while (com.jsoniter.CodegenAccess.nextToken(iter) == ',') {");
- append(lines, "switch (com.jsoniter.CodegenAccess.readObjectFieldAsHash(iter)) {");
- for (Binding field : bindings) {
- for (String fromName : field.fromNames) {
- long hash = 0x811c9dc5;
- for (byte b : fromName.getBytes()) {
- hash ^= b;
- hash *= 0x1000193;
- }
- int intHash = (int) hash;
- append(lines, "case " + intHash + ": ");
- appendBindingSet(lines, desc, field);
- append(lines, "continue;");
- }
- }
- append(lines, "}");
- append(lines, "iter.skip();");
- append(lines, "}");
- if (!desc.ctor.parameters.isEmpty()) {
- append(lines, CodegenImplNative.getTypeName(clazz) + " obj = {{newInst}};");
- for (Binding field : desc.fields) {
- append(lines, String.format("obj.%s = _%s_;", field.field.getName(), field.name));
- }
- for (Binding setter : desc.setters) {
- append(lines, String.format("obj.%s(_%s_);", setter.method.getName(), setter.name));
- }
- }
- appendWrappers(desc.wrappers, lines);
- append(lines, "return obj;");
- return lines.toString()
- .replace("{{clazz}}", clazz.getCanonicalName())
- .replace("{{newInst}}", genNewInstCode(clazz, desc.ctor));
- }
-
- private static void appendBindingSet(StringBuilder lines, ClassDescriptor desc, Binding binding) {
- if (desc.ctor.parameters.isEmpty() && (desc.fields.contains(binding) || desc.setters.contains(binding))) {
- if (binding.valueCanReuse) {
- append(lines, String.format("com.jsoniter.CodegenAccess.setExistingObject(iter, obj.%s);", binding.field.getName()));
- }
- if (binding.field != null) {
- append(lines, String.format("obj.%s = %s;", binding.field.getName(), genField(binding)));
- } else {
- append(lines, String.format("obj.%s(%s);", binding.method.getName(), genField(binding)));
- }
- } else {
- append(lines, String.format("_%s_ = %s;", binding.name, genField(binding)));
- }
- }
-
- private static void appendWrappers(List wrappers, StringBuilder lines) {
- for (WrapperDescriptor wrapper : wrappers) {
- lines.append("obj.");
- lines.append(wrapper.method.getName());
- appendInvocation(lines, wrapper.parameters);
- lines.append(";\n");
- }
- }
-
- private static void appendVarDef(StringBuilder lines, Binding parameter) {
- String typeName = CodegenImplNative.getTypeName(parameter.valueType);
- append(lines, String.format("%s _%s_ = %s;", typeName, parameter.name, DEFAULT_VALUES.get(typeName)));
- }
-
public static String genObjectUsingSkip(Class clazz, ConstructorDescriptor ctor) {
StringBuilder lines = new StringBuilder();
append(lines, "if (iter.readNull()) { return null; }");
@@ -455,112 +364,11 @@ public static String genObjectUsingSkip(Class clazz, ConstructorDescriptor ctor)
append(lines, "return obj;");
return lines.toString()
.replace("{{clazz}}", clazz.getCanonicalName())
- .replace("{{newInst}}", genNewInstCode(clazz, ctor));
+ .replace("{{newInst}}", CodegenImplObjectHash.genNewInstCode(clazz, ctor));
}
- private static String genNewInstCode(Class clazz, ConstructorDescriptor ctor) {
- StringBuilder code = new StringBuilder();
- if (ctor.parameters.isEmpty()) {
- // nothing to bind, safe to reuse existing object
- code.append("(com.jsoniter.CodegenAccess.existingObject(iter) == null ? ");
- }
- if (ctor.objectFactory != null) {
- code.append(String.format("(%s)com.jsoniter.spi.JsoniterSpi.create(%s.class)",
- clazz.getCanonicalName(), clazz.getCanonicalName()));
- } else {
- if (ctor.staticMethodName == null) {
- code.append(String.format("new %s", clazz.getCanonicalName()));
- } else {
- code.append(String.format("%s.%s", clazz.getCanonicalName(), ctor.staticMethodName));
- }
- }
- List params = ctor.parameters;
- if (ctor.objectFactory == null) {
- appendInvocation(code, params);
- }
- if (ctor.parameters.isEmpty()) {
- // nothing to bind, safe to reuse existing obj
- code.append(String.format(" : (%s)com.jsoniter.CodegenAccess.resetExistingObject(iter))", clazz.getCanonicalName()));
- }
- return code.toString();
- }
-
- private static void appendInvocation(StringBuilder code, List params) {
- code.append("(");
- boolean isFirst = true;
- for (Binding ctorParam : params) {
- if (isFirst) {
- isFirst = false;
- } else {
- code.append(",");
- }
- code.append(String.format("_%s_", ctorParam.name));
- }
- code.append(")");
- }
-
- private static void append(StringBuilder lines, String str) {
+ static void append(StringBuilder lines, String str) {
lines.append(str);
lines.append("\n");
}
-
- private static String genField(Binding field) {
- String fieldCacheKey = field.decoderCacheKey();
- // the field decoder might be registered directly
- Decoder decoder = JsoniterSpi.getDecoder(fieldCacheKey);
- Type fieldType = field.valueType;
- if (decoder == null) {
- return String.format("(%s)%s", CodegenImplNative.getTypeName(fieldType), CodegenImplNative.genReadOp(fieldType));
- }
- if (fieldType == boolean.class) {
- if (!(decoder instanceof Decoder.BooleanDecoder)) {
- throw new JsonException("decoder for field " + field + "must implement Decoder.BooleanDecoder");
- }
- return String.format("com.jsoniter.CodegenAccess.readBoolean(\"%s\", iter)", fieldCacheKey);
- }
- if (fieldType == byte.class) {
- if (!(decoder instanceof Decoder.ShortDecoder)) {
- throw new JsonException("decoder for field " + field + "must implement Decoder.ShortDecoder");
- }
- return String.format("com.jsoniter.CodegenAccess.readShort(\"%s\", iter)", fieldCacheKey);
- }
- if (fieldType == short.class) {
- if (!(decoder instanceof Decoder.ShortDecoder)) {
- throw new JsonException("decoder for field " + field + "must implement Decoder.ShortDecoder");
- }
- return String.format("com.jsoniter.CodegenAccess.readShort(\"%s\", iter)", fieldCacheKey);
- }
- if (fieldType == char.class) {
- if (!(decoder instanceof Decoder.IntDecoder)) {
- throw new JsonException("decoder for field " + field + "must implement Decoder.IntDecoder");
- }
- return String.format("com.jsoniter.CodegenAccess.readInt(\"%s\", iter)", fieldCacheKey);
- }
- if (fieldType == int.class) {
- if (!(decoder instanceof Decoder.IntDecoder)) {
- throw new JsonException("decoder for field " + field + "must implement Decoder.IntDecoder");
- }
- return String.format("com.jsoniter.CodegenAccess.readInt(\"%s\", iter)", fieldCacheKey);
- }
- if (fieldType == long.class) {
- if (!(decoder instanceof Decoder.LongDecoder)) {
- throw new JsonException("decoder for field " + field + "must implement Decoder.LongDecoder");
- }
- return String.format("com.jsoniter.CodegenAccess.readLong(\"%s\", iter)", fieldCacheKey);
- }
- if (fieldType == float.class) {
- if (!(decoder instanceof Decoder.FloatDecoder)) {
- throw new JsonException("decoder for field " + field + "must implement Decoder.FloatDecoder");
- }
- return String.format("com.jsoniter.CodegenAccess.readFloat(\"%s\", iter)", fieldCacheKey);
- }
- if (fieldType == double.class) {
- if (!(decoder instanceof Decoder.DoubleDecoder)) {
- throw new JsonException("decoder for field " + field + "must implement Decoder.DoubleDecoder");
- }
- return String.format("com.jsoniter.CodegenAccess.readDouble(\"%s\", iter)", fieldCacheKey);
- }
- return String.format("(%s)com.jsoniter.CodegenAccess.read(\"%s\", iter);",
- CodegenImplNative.getTypeName(fieldType), fieldCacheKey);
- }
}
diff --git a/src/main/java/com/jsoniter/DynamicCodegen.java b/src/main/java/com/jsoniter/DynamicCodegen.java
index 07ea7f73..0e249406 100644
--- a/src/main/java/com/jsoniter/DynamicCodegen.java
+++ b/src/main/java/com/jsoniter/DynamicCodegen.java
@@ -1,15 +1,16 @@
package com.jsoniter;
import com.jsoniter.spi.Decoder;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.CtMethod;
-import javassist.CtNewMethod;
+import javassist.*;
class DynamicCodegen {
static ClassPool pool = ClassPool.getDefault();
+ static {
+ pool.insertClassPath(new ClassClassPath(Decoder.class));
+ }
+
public static Decoder gen(String cacheKey, String source) throws Exception {
Decoder decoder;
CtClass ctClass = pool.makeClass(cacheKey);
diff --git a/src/main/java/com/jsoniter/IterImpl.java b/src/main/java/com/jsoniter/IterImpl.java
index a4e9b02f..ad779fd8 100644
--- a/src/main/java/com/jsoniter/IterImpl.java
+++ b/src/main/java/com/jsoniter/IterImpl.java
@@ -1,29 +1,42 @@
package com.jsoniter;
import com.jsoniter.any.Any;
+import com.jsoniter.spi.JsonException;
+import com.jsoniter.spi.Slice;
import java.io.IOException;
+import java.math.BigInteger;
class IterImpl {
+ private static BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
+ private static BigInteger minLong = BigInteger.valueOf(Long.MIN_VALUE);
+ private static BigInteger maxInt = BigInteger.valueOf(Integer.MAX_VALUE);
+ private static BigInteger minInt = BigInteger.valueOf(Integer.MIN_VALUE);
+
public static final int readObjectFieldAsHash(JsonIterator iter) throws IOException {
- if (nextToken(iter) != '"') {
- throw iter.reportError("readObjectFieldAsHash", "expect \"");
+ if (readByte(iter) != '"') {
+ if (nextToken(iter) != '"') {
+ throw iter.reportError("readObjectFieldAsHash", "expect \"");
+ }
}
long hash = 0x811c9dc5;
- for (int i = iter.head; i < iter.tail; i++) {
+ int i = iter.head;
+ for (; i < iter.tail; i++) {
byte c = iter.buf[i];
if (c == '"') {
- iter.head = i + 1;
- if (nextToken(iter) != ':') {
- throw iter.reportError("readObjectFieldAsHash", "expect :");
- }
- return (int) hash;
+ break;
}
hash ^= c;
hash *= 0x1000193;
}
- throw iter.reportError("readObjectFieldAsHash", "unmatched quote");
+ iter.head = i + 1;
+ if (readByte(iter) != ':') {
+ if (nextToken(iter) != ':') {
+ throw iter.reportError("readObjectFieldAsHash", "expect :");
+ }
+ }
+ return (int) hash;
}
public static final Slice readObjectFieldAsSlice(JsonIterator iter) throws IOException {
@@ -46,7 +59,7 @@ final static void skipArray(JsonIterator iter) throws IOException {
case '[': // If open symbol, increase level
level++;
break;
- case ']': // If close symbol, increase level
+ case ']': // If close symbol, decrease level
level--;
// If we have returned to the original level, we're done
@@ -72,7 +85,7 @@ final static void skipObject(JsonIterator iter) throws IOException {
case '{': // If open symbol, increase level
level++;
break;
- case '}': // If close symbol, increase level
+ case '}': // If close symbol, decrease level
level--;
// If we have returned to the original level, we're done
@@ -112,7 +125,7 @@ final static boolean skipNumber(JsonIterator iter) throws IOException {
boolean dotFound = false;
for (int i = iter.head; i < iter.tail; i++) {
byte c = iter.buf[i];
- if (c == '.') {
+ if (c == '.' || c == 'e' || c == 'E') {
dotFound = true;
continue;
}
@@ -141,36 +154,24 @@ public final static Slice readSlice(JsonIterator iter) throws IOException {
}
}
- final static byte nextToken(JsonIterator iter) throws IOException {
+ final static byte nextToken(final JsonIterator iter) throws IOException {
int i = iter.head;
- try {
- for (; ; ) {
- byte c = iter.buf[i++];
- switch (c) {
- case ' ':
- case '\n':
- case '\r':
- case '\t':
- continue;
- default:
- if (i > iter.tail) {
- iter.head = iter.tail;
- return 0;
- }
- iter.head = i;
- return c;
- }
+ for (; ; ) {
+ byte c = iter.buf[i++];
+ switch (c) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ continue;
+ default:
+ iter.head = i;
+ return c;
}
- } catch (IndexOutOfBoundsException e) {
- iter.head = iter.tail;
- return 0;
}
}
final static byte readByte(JsonIterator iter) throws IOException {
- if (iter.head == iter.tail) {
- return 0;
- }
return iter.buf[iter.head++];
}
@@ -181,30 +182,14 @@ public static Any readAny(JsonIterator iter) throws IOException {
case '"':
skipString(iter);
return Any.lazyString(iter.buf, start, iter.head);
- case '-':
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- if (skipNumber(iter)) {
- return Any.lazyDouble(iter.buf, start, iter.head);
- } else {
- return Any.lazyLong(iter.buf, start, iter.head);
- }
case 't':
- skipUntilBreak(iter);
+ skipFixedBytes(iter, 3);
return Any.wrap(true);
case 'f':
- skipUntilBreak(iter);
+ skipFixedBytes(iter, 4);
return Any.wrap(false);
case 'n':
- skipUntilBreak(iter);
+ skipFixedBytes(iter, 3);
return Any.wrap((Object) null);
case '[':
skipArray(iter);
@@ -213,7 +198,294 @@ public static Any readAny(JsonIterator iter) throws IOException {
skipObject(iter);
return Any.lazyObject(iter.buf, start, iter.head);
default:
- throw iter.reportError("IterImplSkip", "do not know how to skip: " + c);
+ if (skipNumber(iter)) {
+ return Any.lazyDouble(iter.buf, start, iter.head);
+ } else {
+ return Any.lazyLong(iter.buf, start, iter.head);
+ }
+ }
+ }
+
+ public static void skipFixedBytes(JsonIterator iter, int n) throws IOException {
+ iter.head += n;
+ }
+
+ public final static boolean loadMore(JsonIterator iter) throws IOException {
+ return false;
+ }
+
+ public final static int readStringSlowPath(JsonIterator iter, int j) throws IOException {
+ try {
+ boolean isExpectingLowSurrogate = false;
+ for (int i = iter.head; i < iter.tail; ) {
+ int bc = iter.buf[i++];
+ if (bc == '"') {
+ iter.head = i;
+ return j;
+ }
+ if (bc == '\\') {
+ bc = iter.buf[i++];
+ switch (bc) {
+ case 'b':
+ bc = '\b';
+ break;
+ case 't':
+ bc = '\t';
+ break;
+ case 'n':
+ bc = '\n';
+ break;
+ case 'f':
+ bc = '\f';
+ break;
+ case 'r':
+ bc = '\r';
+ break;
+ case '"':
+ case '/':
+ case '\\':
+ break;
+ case 'u':
+ bc = (IterImplString.translateHex(iter.buf[i++]) << 12) +
+ (IterImplString.translateHex(iter.buf[i++]) << 8) +
+ (IterImplString.translateHex(iter.buf[i++]) << 4) +
+ IterImplString.translateHex(iter.buf[i++]);
+ if (Character.isHighSurrogate((char) bc)) {
+ if (isExpectingLowSurrogate) {
+ throw new JsonException("invalid surrogate");
+ } else {
+ isExpectingLowSurrogate = true;
+ }
+ } else if (Character.isLowSurrogate((char) bc)) {
+ if (isExpectingLowSurrogate) {
+ isExpectingLowSurrogate = false;
+ } else {
+ throw new JsonException("invalid surrogate");
+ }
+ } else {
+ if (isExpectingLowSurrogate) {
+ throw new JsonException("invalid surrogate");
+ }
+ }
+ break;
+
+ default:
+ throw iter.reportError("readStringSlowPath", "invalid escape character: " + bc);
+ }
+ } else if ((bc & 0x80) != 0) {
+ final int u2 = iter.buf[i++];
+ if ((bc & 0xE0) == 0xC0) {
+ bc = ((bc & 0x1F) << 6) + (u2 & 0x3F);
+ } else {
+ final int u3 = iter.buf[i++];
+ if ((bc & 0xF0) == 0xE0) {
+ bc = ((bc & 0x0F) << 12) + ((u2 & 0x3F) << 6) + (u3 & 0x3F);
+ } else {
+ final int u4 = iter.buf[i++];
+ if ((bc & 0xF8) == 0xF0) {
+ bc = ((bc & 0x07) << 18) + ((u2 & 0x3F) << 12) + ((u3 & 0x3F) << 6) + (u4 & 0x3F);
+ } else {
+ throw iter.reportError("readStringSlowPath", "invalid unicode character");
+ }
+
+ if (bc >= 0x10000) {
+ // check if valid unicode
+ if (bc >= 0x110000)
+ throw iter.reportError("readStringSlowPath", "invalid unicode character");
+
+ // split surrogates
+ final int sup = bc - 0x10000;
+ if (iter.reusableChars.length == j) {
+ char[] newBuf = new char[iter.reusableChars.length * 2];
+ System.arraycopy(iter.reusableChars, 0, newBuf, 0, iter.reusableChars.length);
+ iter.reusableChars = newBuf;
+ }
+ iter.reusableChars[j++] = (char) ((sup >>> 10) + 0xd800);
+ if (iter.reusableChars.length == j) {
+ char[] newBuf = new char[iter.reusableChars.length * 2];
+ System.arraycopy(iter.reusableChars, 0, newBuf, 0, iter.reusableChars.length);
+ iter.reusableChars = newBuf;
+ }
+ iter.reusableChars[j++] = (char) ((sup & 0x3ff) + 0xdc00);
+ continue;
+ }
+ }
+ }
+ }
+ if (iter.reusableChars.length == j) {
+ char[] newBuf = new char[iter.reusableChars.length * 2];
+ System.arraycopy(iter.reusableChars, 0, newBuf, 0, iter.reusableChars.length);
+ iter.reusableChars = newBuf;
+ }
+ iter.reusableChars[j++] = (char) bc;
+ }
+ throw iter.reportError("readStringSlowPath", "incomplete string");
+ } catch (IndexOutOfBoundsException e) {
+ throw iter.reportError("readString", "incomplete string");
+ }
+ }
+
+ public static int updateStringCopyBound(final JsonIterator iter, final int bound) {
+ return bound;
+ }
+
+ static final int readInt(final JsonIterator iter, final byte c) throws IOException {
+ int ind = IterImplNumber.intDigits[c];
+ if (ind == 0) {
+ IterImplForStreaming.assertNotLeadingZero(iter);
+ return 0;
+ }
+ if (ind == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ throw iter.reportError("readInt", "expect 0~9");
+ }
+ if (iter.tail - iter.head > 9) {
+ int i = iter.head;
+ int ind2 = IterImplNumber.intDigits[iter.buf[i]];
+ if (ind2 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ return -ind;
+ }
+ int ind3 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind3 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 10 + ind2;
+ return -ind;
+ }
+ int ind4 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind4 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 100 + ind2 * 10 + ind3;
+ return -ind;
+ }
+ int ind5 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind5 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 1000 + ind2 * 100 + ind3 * 10 + ind4;
+ return -ind;
+ }
+ int ind6 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind6 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 10000 + ind2 * 1000 + ind3 * 100 + ind4 * 10 + ind5;
+ return -ind;
+ }
+ int ind7 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind7 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 100000 + ind2 * 10000 + ind3 * 1000 + ind4 * 100 + ind5 * 10 + ind6;
+ return -ind;
+ }
+ int ind8 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind8 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 1000000 + ind2 * 100000 + ind3 * 10000 + ind4 * 1000 + ind5 * 100 + ind6 * 10 + ind7;
+ return -ind;
+ }
+ int ind9 = IterImplNumber.intDigits[iter.buf[++i]];
+ ind = ind * 10000000 + ind2 * 1000000 + ind3 * 100000 + ind4 * 10000 + ind5 * 1000 + ind6 * 100 + ind7 * 10 + ind8;
+ iter.head = i;
+ if (ind9 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ return -ind;
+ }
+ }
+ return IterImplForStreaming.readIntSlowPath(iter, ind);
+ }
+
+ static final long readLong(final JsonIterator iter, final byte c) throws IOException {
+ long ind = IterImplNumber.intDigits[c];
+ if (ind == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ throw iter.reportError("readLong", "expect 0~9");
+ }
+ if (iter.tail - iter.head > 9) {
+ int i = iter.head;
+ int ind2 = IterImplNumber.intDigits[iter.buf[i]];
+ if (ind2 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ return -ind;
+ }
+ int ind3 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind3 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 10 + ind2;
+ return -ind;
+ }
+ int ind4 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind4 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 100 + ind2 * 10 + ind3;
+ return -ind;
+ }
+ int ind5 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind5 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 1000 + ind2 * 100 + ind3 * 10 + ind4;
+ return -ind;
+ }
+ int ind6 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind6 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 10000 + ind2 * 1000 + ind3 * 100 + ind4 * 10 + ind5;
+ return -ind;
+ }
+ int ind7 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind7 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 100000 + ind2 * 10000 + ind3 * 1000 + ind4 * 100 + ind5 * 10 + ind6;
+ return -ind;
+ }
+ int ind8 = IterImplNumber.intDigits[iter.buf[++i]];
+ if (ind8 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ ind = ind * 1000000 + ind2 * 100000 + ind3 * 10000 + ind4 * 1000 + ind5 * 100 + ind6 * 10 + ind7;
+ return -ind;
+ }
+ int ind9 = IterImplNumber.intDigits[iter.buf[++i]];
+ ind = ind * 10000000 + ind2 * 1000000 + ind3 * 100000 + ind4 * 10000 + ind5 * 1000 + ind6 * 100 + ind7 * 10 + ind8;
+ iter.head = i;
+ if (ind9 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ return -ind;
+ }
+ }
+ return IterImplForStreaming.readLongSlowPath(iter, ind);
+ }
+
+ static final double readDouble(final JsonIterator iter) throws IOException {
+ int oldHead = iter.head;
+ try {
+ try {
+ long value = IterImplNumber.readLong(iter); // without the dot & sign
+ if (iter.head == iter.tail) {
+ return value;
+ }
+ byte c = iter.buf[iter.head];
+ if (c == '.') {
+ iter.head++;
+ int start = iter.head;
+ c = iter.buf[iter.head++];
+ long decimalPart = readLong(iter, c);
+ if (decimalPart == Long.MIN_VALUE) {
+ return IterImplForStreaming.readDoubleSlowPath(iter);
+ }
+ decimalPart = -decimalPart;
+ int decimalPlaces = iter.head - start;
+ if (decimalPlaces > 0 && decimalPlaces < IterImplNumber.POW10.length && (iter.head - oldHead) < 10) {
+ return value + (decimalPart / (double) IterImplNumber.POW10[decimalPlaces]);
+ } else {
+ iter.head = oldHead;
+ return IterImplForStreaming.readDoubleSlowPath(iter);
+ }
+ } else {
+ return value;
+ }
+ } finally {
+ if (iter.head < iter.tail && (iter.buf[iter.head] == 'e' || iter.buf[iter.head] == 'E')) {
+ iter.head = oldHead;
+ return IterImplForStreaming.readDoubleSlowPath(iter);
+ }
+ }
+ } catch (JsonException e) {
+ iter.head = oldHead;
+ return IterImplForStreaming.readDoubleSlowPath(iter);
}
}
}
diff --git a/src/main/java/com/jsoniter/IterImplArray.java b/src/main/java/com/jsoniter/IterImplArray.java
new file mode 100644
index 00000000..80120a2c
--- /dev/null
+++ b/src/main/java/com/jsoniter/IterImplArray.java
@@ -0,0 +1,51 @@
+package com.jsoniter;
+
+import java.io.IOException;
+
+class IterImplArray {
+
+ public static final boolean readArray(final JsonIterator iter) throws IOException {
+ byte c = IterImpl.nextToken(iter);
+ switch (c) {
+ case '[':
+ c = IterImpl.nextToken(iter);
+ if (c != ']') {
+ iter.unreadByte();
+ return true;
+ }
+ return false;
+ case ']':
+ return false;
+ case ',':
+ return true;
+ case 'n':
+ return false;
+ default:
+ throw iter.reportError("readArray", "expect [ or , or n or ], but found: " + (char) c);
+ }
+ }
+
+ public static final boolean readArrayCB(final JsonIterator iter, final JsonIterator.ReadArrayCallback callback, Object attachment) throws IOException {
+ byte c = IterImpl.nextToken(iter);
+ if (c == '[') {
+ c = IterImpl.nextToken(iter);
+ if (c != ']') {
+ iter.unreadByte();
+ if (!callback.handle(iter, attachment)) {
+ return false;
+ }
+ while (IterImpl.nextToken(iter) == ',') {
+ if (!callback.handle(iter, attachment)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return true;
+ }
+ if (c == 'n') {
+ return true;
+ }
+ throw iter.reportError("readArrayCB", "expect [ or n, but found: " + (char) c);
+ }
+}
diff --git a/src/main/java/com/jsoniter/IterImplForStreaming.java b/src/main/java/com/jsoniter/IterImplForStreaming.java
index 54b068f9..2cef3a16 100644
--- a/src/main/java/com/jsoniter/IterImplForStreaming.java
+++ b/src/main/java/com/jsoniter/IterImplForStreaming.java
@@ -1,6 +1,8 @@
package com.jsoniter;
import com.jsoniter.any.Any;
+import com.jsoniter.spi.JsonException;
+import com.jsoniter.spi.Slice;
import java.io.IOException;
@@ -36,9 +38,6 @@ public static final int readObjectFieldAsHash(JsonIterator iter) throws IOExcept
}
public static final Slice readObjectFieldAsSlice(JsonIterator iter) throws IOException {
- if (nextToken(iter) != '"') {
- throw iter.reportError("readObjectFieldAsSlice", "expect \"");
- }
Slice field = readSlice(iter);
boolean notCopied = field != null;
if (CodegenAccess.skipWhitespacesWithoutLoadMore(iter)) {
@@ -72,7 +71,7 @@ final static void skipArray(JsonIterator iter) throws IOException {
case '[': // If open symbol, increase level
level++;
break;
- case ']': // If close symbol, increase level
+ case ']': // If close symbol, decrease level
level--;
// If we have returned to the original level, we're done
@@ -102,7 +101,7 @@ final static void skipObject(JsonIterator iter) throws IOException {
case '{': // If open symbol, increase level
level++;
break;
- case '}': // If close symbol, increase level
+ case '}': // If close symbol, decrease level
level--;
// If we have returned to the original level, we're done
@@ -125,7 +124,10 @@ final static void skipString(JsonIterator iter) throws IOException {
if (end == -1) {
int j = iter.tail - 1;
boolean escaped = true;
+ // can not just look the last byte is \
+ // because it could be \\ or \\\
for (; ; ) {
+ // walk backward until head
if (j < iter.head || iter.buf[j] != '\\') {
// even number of backslashes
// either end of buffer, or " found
@@ -142,10 +144,11 @@ final static void skipString(JsonIterator iter) throws IOException {
}
if (!loadMore(iter)) {
- return;
+ throw iter.reportError("skipString", "incomplete string");
}
if (escaped) {
- iter.head = 1; // skip the first char as last char readAny is \
+ // TODO add unit test to prove/verify bug
+ iter.head += 1; // skip the first char as last char is \
}
} else {
iter.head = end;
@@ -177,7 +180,7 @@ final static boolean skipNumber(JsonIterator iter) throws IOException {
for (; ; ) {
for (int i = iter.head; i < iter.tail; i++) {
byte c = iter.buf[i];
- if (c == '.') {
+ if (c == '.' || c == 'e' || c == 'E') {
dotFound = true;
continue;
}
@@ -195,6 +198,9 @@ final static boolean skipNumber(JsonIterator iter) throws IOException {
// read the bytes between " "
final static Slice readSlice(JsonIterator iter) throws IOException {
+ if (IterImpl.nextToken(iter) != '"') {
+ throw iter.reportError("readSlice", "expect \" for string");
+ }
int end = IterImplString.findSliceEnd(iter);
if (end != -1) {
// reuse current buffer
@@ -247,8 +253,7 @@ final static byte nextToken(JsonIterator iter) throws IOException {
}
}
-
- final static boolean loadMore(JsonIterator iter) throws IOException {
+ public final static boolean loadMore(JsonIterator iter) throws IOException {
if (iter.in == null) {
return false;
}
@@ -270,19 +275,19 @@ final static boolean loadMore(JsonIterator iter) throws IOException {
}
private static boolean keepSkippedBytesThenRead(JsonIterator iter) throws IOException {
- int n;
- int offset;
- if (iter.skipStartedAt == 0 || iter.skipStartedAt < iter.tail / 2) {
- byte[] newBuf = new byte[iter.buf.length * 2];
- offset = iter.tail - iter.skipStartedAt;
- System.arraycopy(iter.buf, iter.skipStartedAt, newBuf, 0, offset);
- iter.buf = newBuf;
- n = iter.in.read(iter.buf, offset, iter.buf.length - offset);
- } else {
- offset = iter.tail - iter.skipStartedAt;
- System.arraycopy(iter.buf, iter.skipStartedAt, iter.buf, 0, offset);
- n = iter.in.read(iter.buf, offset, iter.buf.length - offset);
+ int offset = iter.tail - iter.skipStartedAt;
+ byte[] srcBuffer = iter.buf;
+ // Check there is no unused buffer capacity
+ if ((getUnusedBufferByteCount(iter)) == 0) {
+ // If auto expand buffer enabled, then create larger buffer
+ if (iter.autoExpandBufferStep > 0) {
+ iter.buf = new byte[iter.buf.length + iter.autoExpandBufferStep];
+ } else {
+ throw iter.reportError("loadMore", String.format("buffer is full and autoexpansion is disabled. tail: [%s] skipStartedAt: [%s]", iter.tail, iter.skipStartedAt));
+ }
}
+ System.arraycopy(srcBuffer, iter.skipStartedAt, iter.buf, 0, offset);
+ int n = iter.in.read(iter.buf, offset, iter.buf.length - offset);
iter.skipStartedAt = 0;
if (n < 1) {
if (n == -1) {
@@ -297,10 +302,15 @@ private static boolean keepSkippedBytesThenRead(JsonIterator iter) throws IOExce
return true;
}
+ private static int getUnusedBufferByteCount(JsonIterator iter) {
+ // Get bytes from 0 to skipStart + from tail till end
+ return iter.buf.length - iter.tail + iter.skipStartedAt;
+ }
+
final static byte readByte(JsonIterator iter) throws IOException {
if (iter.head == iter.tail) {
if (!loadMore(iter)) {
- return 0;
+ throw iter.reportError("readByte", "no more to read");
}
}
return iter.buf[iter.head++];
@@ -315,36 +325,18 @@ public static Any readAny(JsonIterator iter) throws IOException {
skipString(iter);
byte[] copied = copySkippedBytes(iter);
return Any.lazyString(copied, 0, copied.length);
- case '-':
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- if (skipNumber(iter)) {
- copied = copySkippedBytes(iter);
- return Any.lazyDouble(copied, 0, copied.length);
- } else {
- copied = copySkippedBytes(iter);
- return Any.lazyLong(copied, 0, copied.length);
- }
case 't':
- skipUntilBreak(iter);
+ skipFixedBytes(iter, 3);
iter.skipStartedAt = -1;
return Any.wrap(true);
case 'f':
- skipUntilBreak(iter);
+ skipFixedBytes(iter, 4);
iter.skipStartedAt = -1;
return Any.wrap(false);
case 'n':
- skipUntilBreak(iter);
+ skipFixedBytes(iter, 3);
iter.skipStartedAt = -1;
- return Any.wrap((Object)null);
+ return Any.wrap((Object) null);
case '[':
skipArray(iter);
copied = copySkippedBytes(iter);
@@ -354,7 +346,13 @@ public static Any readAny(JsonIterator iter) throws IOException {
copied = copySkippedBytes(iter);
return Any.lazyObject(copied, 0, copied.length);
default:
- throw iter.reportError("IterImplSkip", "do not know how to skip: " + c);
+ if (skipNumber(iter)) {
+ copied = copySkippedBytes(iter);
+ return Any.lazyDouble(copied, 0, copied.length);
+ } else {
+ copied = copySkippedBytes(iter);
+ return Any.lazyLong(copied, 0, copied.length);
+ }
}
}
@@ -366,4 +364,300 @@ private static byte[] copySkippedBytes(JsonIterator iter) {
System.arraycopy(iter.buf, start, bytes, 0, bytes.length);
return bytes;
}
+
+ public static void skipFixedBytes(JsonIterator iter, int n) throws IOException {
+ iter.head += n;
+ if (iter.head >= iter.tail) {
+ int more = iter.head - iter.tail;
+ if (!loadMore(iter)) {
+ if (more == 0) {
+ iter.head = iter.tail;
+ return;
+ }
+ throw iter.reportError("skipFixedBytes", "unexpected end");
+ }
+ iter.head += more;
+ }
+ }
+
+ public static int updateStringCopyBound(final JsonIterator iter, final int bound) {
+ if (bound > iter.tail - iter.head) {
+ return iter.tail - iter.head;
+ } else {
+ return bound;
+ }
+ }
+
+ public final static int readStringSlowPath(JsonIterator iter, int j) throws IOException {
+ boolean isExpectingLowSurrogate = false;
+ for (;;) {
+ int bc = readByte(iter);
+ if (bc == '"') {
+ return j;
+ }
+ if (bc == '\\') {
+ bc = readByte(iter);
+ switch (bc) {
+ case 'b':
+ bc = '\b';
+ break;
+ case 't':
+ bc = '\t';
+ break;
+ case 'n':
+ bc = '\n';
+ break;
+ case 'f':
+ bc = '\f';
+ break;
+ case 'r':
+ bc = '\r';
+ break;
+ case '"':
+ case '/':
+ case '\\':
+ break;
+ case 'u':
+ bc = (IterImplString.translateHex(readByte(iter)) << 12) +
+ (IterImplString.translateHex(readByte(iter)) << 8) +
+ (IterImplString.translateHex(readByte(iter)) << 4) +
+ IterImplString.translateHex(readByte(iter));
+ if (Character.isHighSurrogate((char) bc)) {
+ if (isExpectingLowSurrogate) {
+ throw new JsonException("invalid surrogate");
+ } else {
+ isExpectingLowSurrogate = true;
+ }
+ } else if (Character.isLowSurrogate((char) bc)) {
+ if (isExpectingLowSurrogate) {
+ isExpectingLowSurrogate = false;
+ } else {
+ throw new JsonException("invalid surrogate");
+ }
+ } else {
+ if (isExpectingLowSurrogate) {
+ throw new JsonException("invalid surrogate");
+ }
+ }
+ break;
+
+ default:
+ throw iter.reportError("readStringSlowPath", "invalid escape character: " + bc);
+ }
+ } else if ((bc & 0x80) != 0) {
+ final int u2 = readByte(iter);
+ if ((bc & 0xE0) == 0xC0) {
+ bc = ((bc & 0x1F) << 6) + (u2 & 0x3F);
+ } else {
+ final int u3 = readByte(iter);
+ if ((bc & 0xF0) == 0xE0) {
+ bc = ((bc & 0x0F) << 12) + ((u2 & 0x3F) << 6) + (u3 & 0x3F);
+ } else {
+ final int u4 = readByte(iter);
+ if ((bc & 0xF8) == 0xF0) {
+ bc = ((bc & 0x07) << 18) + ((u2 & 0x3F) << 12) + ((u3 & 0x3F) << 6) + (u4 & 0x3F);
+ } else {
+ throw iter.reportError("readStringSlowPath", "invalid unicode character");
+ }
+
+ if (bc >= 0x10000) {
+ // check if valid unicode
+ if (bc >= 0x110000)
+ throw iter.reportError("readStringSlowPath", "invalid unicode character");
+
+ // split surrogates
+ final int sup = bc - 0x10000;
+ if (iter.reusableChars.length == j) {
+ char[] newBuf = new char[iter.reusableChars.length * 2];
+ System.arraycopy(iter.reusableChars, 0, newBuf, 0, iter.reusableChars.length);
+ iter.reusableChars = newBuf;
+ }
+ iter.reusableChars[j++] = (char) ((sup >>> 10) + 0xd800);
+ if (iter.reusableChars.length == j) {
+ char[] newBuf = new char[iter.reusableChars.length * 2];
+ System.arraycopy(iter.reusableChars, 0, newBuf, 0, iter.reusableChars.length);
+ iter.reusableChars = newBuf;
+ }
+ iter.reusableChars[j++] = (char) ((sup & 0x3ff) + 0xdc00);
+ continue;
+ }
+ }
+ }
+ }
+ if (iter.reusableChars.length == j) {
+ char[] newBuf = new char[iter.reusableChars.length * 2];
+ System.arraycopy(iter.reusableChars, 0, newBuf, 0, iter.reusableChars.length);
+ iter.reusableChars = newBuf;
+ }
+ iter.reusableChars[j++] = (char) bc;
+ }
+ }
+
+ static long readLongSlowPath(final JsonIterator iter, long value) throws IOException {
+ value = -value; // add negatives to avoid redundant checks for Long.MIN_VALUE on each iteration
+ long multmin = -922337203685477580L; // limit / 10
+ for (; ; ) {
+ for (int i = iter.head; i < iter.tail; i++) {
+ int ind = IterImplNumber.intDigits[iter.buf[i]];
+ if (ind == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ return value;
+ }
+ if (value < multmin) {
+ throw iter.reportError("readLongSlowPath", "value is too large for long");
+ }
+ value = (value << 3) + (value << 1) - ind;
+ if (value >= 0) {
+ throw iter.reportError("readLongSlowPath", "value is too large for long");
+ }
+ }
+ if (!IterImpl.loadMore(iter)) {
+ iter.head = iter.tail;
+ return value;
+ }
+ }
+ }
+
+ static int readIntSlowPath(final JsonIterator iter, int value) throws IOException {
+ value = -value; // add negatives to avoid redundant checks for Integer.MIN_VALUE on each iteration
+ int multmin = -214748364; // limit / 10
+ for (; ; ) {
+ for (int i = iter.head; i < iter.tail; i++) {
+ int ind = IterImplNumber.intDigits[iter.buf[i]];
+ if (ind == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ iter.head = i;
+ return value;
+ }
+ if (value < multmin) {
+ throw iter.reportError("readIntSlowPath", "value is too large for int");
+ }
+ value = (value << 3) + (value << 1) - ind;
+ if (value >= 0) {
+ throw iter.reportError("readIntSlowPath", "value is too large for int");
+ }
+ }
+ if (!IterImpl.loadMore(iter)) {
+ iter.head = iter.tail;
+ return value;
+ }
+ }
+ }
+
+ public static final double readDoubleSlowPath(final JsonIterator iter) throws IOException {
+ try {
+ numberChars numberChars = readNumber(iter);
+ if (numberChars.charsLength == 0 && iter.whatIsNext() == ValueType.STRING) {
+ String possibleInf = iter.readString();
+ if ("infinity".equals(possibleInf)) {
+ return Double.POSITIVE_INFINITY;
+ }
+ if ("-infinity".equals(possibleInf)) {
+ return Double.NEGATIVE_INFINITY;
+ }
+ throw iter.reportError("readDoubleSlowPath", "expect number but found string: " + possibleInf);
+ }
+ return Double.valueOf(new String(numberChars.chars, 0, numberChars.charsLength));
+ } catch (NumberFormatException e) {
+ throw iter.reportError("readDoubleSlowPath", e.toString());
+ }
+ }
+
+ static class numberChars {
+ char[] chars;
+ int charsLength;
+ boolean dotFound;
+ }
+
+ public static final numberChars readNumber(final JsonIterator iter) throws IOException {
+ int j = 0;
+ boolean dotFound = false;
+ for (; ; ) {
+ for (int i = iter.head; i < iter.tail; i++) {
+ if (j == iter.reusableChars.length) {
+ char[] newBuf = new char[iter.reusableChars.length * 2];
+ System.arraycopy(iter.reusableChars, 0, newBuf, 0, iter.reusableChars.length);
+ iter.reusableChars = newBuf;
+ }
+ byte c = iter.buf[i];
+ switch (c) {
+ case '.':
+ case 'e':
+ case 'E':
+ dotFound = true;
+ // fallthrough
+ case '-':
+ case '+':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ iter.reusableChars[j++] = (char) c;
+ break;
+ default:
+ iter.head = i;
+ numberChars numberChars = new numberChars();
+ numberChars.chars = iter.reusableChars;
+ numberChars.charsLength = j;
+ numberChars.dotFound = dotFound;
+ return numberChars;
+ }
+ }
+ if (!IterImpl.loadMore(iter)) {
+ iter.head = iter.tail;
+ numberChars numberChars = new numberChars();
+ numberChars.chars = iter.reusableChars;
+ numberChars.charsLength = j;
+ numberChars.dotFound = dotFound;
+ return numberChars;
+ }
+ }
+ }
+
+ static final double readDouble(final JsonIterator iter) throws IOException {
+ return readDoubleSlowPath(iter);
+ }
+
+ static final long readLong(final JsonIterator iter, final byte c) throws IOException {
+ long ind = IterImplNumber.intDigits[c];
+ if (ind == 0) {
+ assertNotLeadingZero(iter);
+ return 0;
+ }
+ if (ind == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ throw iter.reportError("readLong", "expect 0~9");
+ }
+ return IterImplForStreaming.readLongSlowPath(iter, ind);
+ }
+
+ static final int readInt(final JsonIterator iter, final byte c) throws IOException {
+ int ind = IterImplNumber.intDigits[c];
+ if (ind == 0) {
+ assertNotLeadingZero(iter);
+ return 0;
+ }
+ if (ind == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ throw iter.reportError("readInt", "expect 0~9");
+ }
+ return IterImplForStreaming.readIntSlowPath(iter, ind);
+ }
+
+ static void assertNotLeadingZero(JsonIterator iter) throws IOException {
+ try {
+ byte nextByte = iter.buf[iter.head];
+ int ind2 = IterImplNumber.intDigits[nextByte];
+ if (ind2 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) {
+ return;
+ }
+ throw iter.reportError("assertNotLeadingZero", "leading zero is invalid");
+ } catch (ArrayIndexOutOfBoundsException e) {
+ iter.head = iter.tail;
+ return;
+ }
+ }
}
diff --git a/src/main/java/com/jsoniter/IterImplNumber.java b/src/main/java/com/jsoniter/IterImplNumber.java
index 46b96916..c521cdd5 100644
--- a/src/main/java/com/jsoniter/IterImplNumber.java
+++ b/src/main/java/com/jsoniter/IterImplNumber.java
@@ -1,337 +1,112 @@
+/*
+this implementations contains significant code from https://github.com/ngs-doo/dsl-json/blob/master/LICENSE
+
+Copyright (c) 2015, Nova Generacija Softvera d.o.o.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of Nova Generacija Softvera d.o.o. nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.jsoniter;
import java.io.IOException;
class IterImplNumber {
-
- final static int[] digits = new int[256];
+
+ final static int[] intDigits = new int[127];
+ final static int[] floatDigits = new int[127];
+ final static int END_OF_NUMBER = -2;
+ final static int DOT_IN_NUMBER = -3;
+ final static int INVALID_CHAR_FOR_NUMBER = -1;
+ static final long POW10[] = {
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
+ 1000000000, 10000000000L, 100000000000L, 1000000000000L,
+ 10000000000000L, 100000000000000L, 1000000000000000L};
static {
- for (int i = 0; i < digits.length; i++) {
- digits[i] = -1;
+ for (int i = 0; i < floatDigits.length; i++) {
+ floatDigits[i] = INVALID_CHAR_FOR_NUMBER;
+ intDigits[i] = INVALID_CHAR_FOR_NUMBER;
}
for (int i = '0'; i <= '9'; ++i) {
- digits[i] = (i - '0');
- }
- for (int i = 'a'; i <= 'f'; ++i) {
- digits[i] = ((i - 'a') + 10);
- }
- for (int i = 'A'; i <= 'F'; ++i) {
- digits[i] = ((i - 'A') + 10);
- }
- }
-
- public static final double readDouble(JsonIterator iter) throws IOException {
- final byte c = IterImpl.nextToken(iter);
- // when re-read using slowpath, it should include the first byte
- iter.unreadByte();
- if (c == '-') {
- // skip '-' by + 1
- return readNegativeDouble(iter, iter.head + 1);
- }
- return readPositiveDouble(iter, iter.head);
- }
-
- private static final double readPositiveDouble(JsonIterator iter, int start) throws IOException {
- long value = 0;
- byte c = ' ';
- int i = start;
- for (; i < iter.tail; i++) {
- c = iter.buf[i];
- if (c == ',' || c == '}' || c == ']' || c == ' ') {
- iter.head = i;
- return value;
- }
- if (c == '.') break;
- final int ind = digits[c];
- value = (value << 3) + (value << 1) + ind;
- if (ind < 0 || ind > 9) {
- return readDoubleSlowPath(iter);
- }
- }
- if (c == '.') {
- i++;
- long div = 1;
- for (; i < iter.tail; i++) {
- c = iter.buf[i];
- if (c == ',' || c == '}' || c == ']' || c == ' ') {
- iter.head = i;
- return value / (double) div;
- }
- final int ind = digits[c];
- div = (div << 3) + (div << 1);
- value = (value << 3) + (value << 1) + ind;
- if (ind < 0 || ind > 9) {
- return readDoubleSlowPath(iter);
- }
- }
- }
- return readDoubleSlowPath(iter);
- }
-
- private static final double readNegativeDouble(JsonIterator iter, int start) throws IOException {
- long value = 0;
- byte c = ' ';
- int i = start;
- for (; i < iter.tail; i++) {
- c = iter.buf[i];
- if (c == ',' || c == '}' || c == ']' || c == ' ') {
- iter.head = i;
- return value;
- }
- if (c == '.') break;
- final int ind = digits[c];
- value = (value << 3) + (value << 1) - ind;
- if (ind < 0 || ind > 9) {
- return readDoubleSlowPath(iter);
- }
- }
- if (c == '.') {
- i++;
- long div = 1;
- for (; i < iter.tail; i++) {
- c = iter.buf[i];
- if (c == ',' || c == '}' || c == ']' || c == ' ') {
- iter.head = i;
- return value / (double) div;
- }
- final int ind = digits[c];
- div = (div << 3) + (div << 1);
- value = (value << 3) + (value << 1) - ind;
- if (ind < 0 || ind > 9) {
- return readDoubleSlowPath(iter);
- }
- }
- }
- return readDoubleSlowPath(iter);
+ floatDigits[i] = (i - '0');
+ intDigits[i] = (i - '0');
+ }
+ floatDigits[','] = END_OF_NUMBER;
+ floatDigits[']'] = END_OF_NUMBER;
+ floatDigits['}'] = END_OF_NUMBER;
+ floatDigits[' '] = END_OF_NUMBER;
+ floatDigits['.'] = DOT_IN_NUMBER;
}
- public static final double readDoubleSlowPath(JsonIterator iter) throws IOException {
- try {
- return Double.valueOf(readNumber(iter));
- } catch (NumberFormatException e) {
- throw iter.reportError("readDoubleSlowPath", e.toString());
- }
- }
-
- public static final float readFloat(JsonIterator iter) throws IOException {
+ public static final double readDouble(final JsonIterator iter) throws IOException {
final byte c = IterImpl.nextToken(iter);
- // when re-read using slowpath, it should include the first byte
- iter.unreadByte();
if (c == '-') {
- // skip '-' by + 1
- return readNegativeFloat(iter, iter.head + 1);
- }
- return readPositiveFloat(iter, iter.head);
- }
-
- private static final float readPositiveFloat(JsonIterator iter, int start) throws IOException {
- long value = 0;
- byte c = ' ';
- int i = start;
- for (; i < iter.tail; i++) {
- c = iter.buf[i];
- if (c == ',' || c == '}' || c == ']' || c == ' ') {
- iter.head = i;
- return value;
- }
- if (c == '.') break;
- final int ind = digits[c];
- value = (value << 3) + (value << 1) + ind;
- if (ind < 0 || ind > 9) {
- return readFloatSlowPath(iter);
- }
- }
- if (c == '.') {
- i++;
- long div = 1;
- for (; i < iter.tail; i++) {
- c = iter.buf[i];
- if (c == ',' || c == '}' || c == ']' || c == ' ') {
- iter.head = i;
- return value / (float) div;
- }
- final int ind = digits[c];
- div = (div << 3) + (div << 1);
- value = (value << 3) + (value << 1) + ind;
- if (ind < 0 || ind > 9) {
- return readFloatSlowPath(iter);
- }
- }
- }
- return readFloatSlowPath(iter);
- }
-
- private static final float readNegativeFloat(JsonIterator iter, int start) throws IOException {
- long value = 0;
- byte c = ' ';
- int i = start;
- for (; i < iter.tail; i++) {
- c = iter.buf[i];
- if (c == ',' || c == '}' || c == ']' || c == ' ') {
- iter.head = i;
- return value;
- }
- if (c == '.') break;
- final int ind = digits[c];
- value = (value << 3) + (value << 1) - ind;
- if (ind < 0 || ind > 9) {
- return readFloatSlowPath(iter);
- }
- }
- if (c == '.') {
- i++;
- long div = 1;
- for (; i < iter.tail; i++) {
- c = iter.buf[i];
- if (c == ',' || c == '}' || c == ']' || c == ' ') {
- iter.head = i;
- return value / (float) div;
- }
- final int ind = digits[c];
- div = (div << 3) + (div << 1);
- value = (value << 3) + (value << 1) - ind;
- if (ind < 0 || ind > 9) {
- return readFloatSlowPath(iter);
- }
- }
- }
- return readFloatSlowPath(iter);
- }
-
- public static final float readFloatSlowPath(JsonIterator iter) throws IOException {
- try {
- return Float.valueOf(readNumber(iter));
- } catch (NumberFormatException e) {
- throw iter.reportError("readDoubleSlowPath", e.toString());
+ return -IterImpl.readDouble(iter);
+ } else {
+ iter.unreadByte();
+ return IterImpl.readDouble(iter);
}
}
- public static final String readNumber(JsonIterator iter) throws IOException {
- int j = 0;
- for (byte c = IterImpl.nextToken(iter); ; c = IterImpl.readByte(iter)) {
- if (j == iter.reusableChars.length) {
- char[] newBuf = new char[iter.reusableChars.length * 2];
- System.arraycopy(iter.reusableChars, 0, newBuf, 0, iter.reusableChars.length);
- iter.reusableChars = newBuf;
- }
- switch (c) {
- case '-':
- case '+':
- case '.':
- case 'e':
- case 'E':
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- iter.reusableChars[j++] = (char) c;
- break;
- case 0:
- return new String(iter.reusableChars, 0, j);
- default:
- iter.unreadByte();
- return new String(iter.reusableChars, 0, j);
- }
- }
+ public static final float readFloat(final JsonIterator iter) throws IOException {
+ return (float) IterImplNumber.readDouble(iter);
}
- public static final int readInt(JsonIterator iter) throws IOException {
+ public static final int readInt(final JsonIterator iter) throws IOException {
byte c = IterImpl.nextToken(iter);
if (c == '-') {
- return -readUnsignedInt(iter);
+ return IterImpl.readInt(iter, IterImpl.readByte(iter));
} else {
- iter.unreadByte();
- return readUnsignedInt(iter);
- }
- }
-
- public static final int readUnsignedInt(JsonIterator iter) throws IOException {
- // TODO: throw overflow
- byte c = IterImpl.readByte(iter);
- int v = digits[c];
- if (v == 0) {
- return 0;
- }
- if (v == -1) {
- throw iter.reportError("readUnsignedInt", "expect 0~9");
- }
- int result = 0;
- for (; ; ) {
- result = result * 10 + v;
- c = IterImpl.readByte(iter);
- v = digits[c];
- if (v == -1) {
- iter.unreadByte();
- break;
+ int val = IterImpl.readInt(iter, c);
+ if (val == Integer.MIN_VALUE) {
+ throw iter.reportError("readInt", "value is too large for int");
}
+ return -val;
}
- return result;
}
public static final long readLong(JsonIterator iter) throws IOException {
byte c = IterImpl.nextToken(iter);
if (c == '-') {
- return -readUnsignedLong(iter);
- } else {
- iter.unreadByte();
- return readUnsignedLong(iter);
- }
- }
-
- public static final long readUnsignedLong(JsonIterator iter) throws IOException {
- // TODO: throw overflow
- byte c = IterImpl.readByte(iter);
- int v = digits[c];
- if (v == 0) {
- return 0;
- }
- if (v == -1) {
- throw iter.reportError("readUnsignedLong", "expect 0~9");
- }
- long result = 0;
- for (; ; ) {
- result = result * 10 + v;
c = IterImpl.readByte(iter);
- v = digits[c];
- if (v == -1) {
- iter.unreadByte();
- break;
+ if (IterImplNumber.intDigits[c] == 0) {
+ IterImplForStreaming.assertNotLeadingZero(iter);
+ return 0;
}
+ return IterImpl.readLong(iter, c);
+ } else {
+ if (IterImplNumber.intDigits[c] == 0) {
+ IterImplForStreaming.assertNotLeadingZero(iter);
+ return 0;
+ }
+ long val = IterImpl.readLong(iter, c);
+ if (val == Long.MIN_VALUE) {
+ throw iter.reportError("readLong", "value is too large for long");
+ }
+ return -val;
}
- return result;
- }
-
- public static final char readU4(JsonIterator iter) throws IOException {
- int v = digits[IterImpl.readByte(iter)];
- if (v == -1) {
- throw iter.reportError("readU4", "bad unicode");
- }
- char b = (char) v;
- v = digits[IterImpl.readByte(iter)];
- if (v == -1) {
- throw iter.reportError("readU4", "bad unicode");
- }
- b = (char) (b << 4);
- b += v;
- v = digits[IterImpl.readByte(iter)];
- if (v == -1) {
- throw iter.reportError("readU4", "bad unicode");
- }
- b = (char) (b << 4);
- b += v;
- v = digits[IterImpl.readByte(iter)];
- if (v == -1) {
- throw iter.reportError("readU4", "bad unicode");
- }
- b = (char) (b << 4);
- b += v;
- return b;
}
}
diff --git a/src/main/java/com/jsoniter/IterImplObject.java b/src/main/java/com/jsoniter/IterImplObject.java
new file mode 100644
index 00000000..64468d94
--- /dev/null
+++ b/src/main/java/com/jsoniter/IterImplObject.java
@@ -0,0 +1,75 @@
+package com.jsoniter;
+
+import java.io.IOException;
+
+class IterImplObject {
+
+ public static final String readObject(JsonIterator iter) throws IOException {
+ byte c = IterImpl.nextToken(iter);
+ switch (c) {
+ case 'n':
+ IterImpl.skipFixedBytes(iter, 3);
+ return null;
+ case '{':
+ c = IterImpl.nextToken(iter);
+ if (c == '"') {
+ iter.unreadByte();
+ String field = iter.readString();
+ if (IterImpl.nextToken(iter) != ':') {
+ throw iter.reportError("readObject", "expect :");
+ }
+ return field;
+ }
+ if (c == '}') {
+ return null; // end of object
+ }
+ throw iter.reportError("readObject", "expect \" after {");
+ case ',':
+ String field = iter.readString();
+ if (IterImpl.nextToken(iter) != ':') {
+ throw iter.reportError("readObject", "expect :");
+ }
+ return field;
+ case '}':
+ return null; // end of object
+ default:
+ throw iter.reportError("readObject", "expect { or , or } or n, but found: " + (char)c);
+ }
+ }
+
+ public static final boolean readObjectCB(JsonIterator iter, JsonIterator.ReadObjectCallback cb, Object attachment) throws IOException {
+ byte c = IterImpl.nextToken(iter);
+ if ('{' == c) {
+ c = IterImpl.nextToken(iter);
+ if ('"' == c) {
+ iter.unreadByte();
+ String field = iter.readString();
+ if (IterImpl.nextToken(iter) != ':') {
+ throw iter.reportError("readObject", "expect :");
+ }
+ if (!cb.handle(iter, field, attachment)) {
+ return false;
+ }
+ while (IterImpl.nextToken(iter) == ',') {
+ field = iter.readString();
+ if (IterImpl.nextToken(iter) != ':') {
+ throw iter.reportError("readObject", "expect :");
+ }
+ if (!cb.handle(iter, field, attachment)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if ('}' == c) {
+ return true;
+ }
+ throw iter.reportError("readObjectCB", "expect \" after {");
+ }
+ if ('n' == c) {
+ IterImpl.skipFixedBytes(iter, 3);
+ return true;
+ }
+ throw iter.reportError("readObjectCB", "expect { or n");
+ }
+}
diff --git a/src/main/java/com/jsoniter/IterImplSkip.java b/src/main/java/com/jsoniter/IterImplSkip.java
index e9ecceda..c9a82afd 100644
--- a/src/main/java/com/jsoniter/IterImplSkip.java
+++ b/src/main/java/com/jsoniter/IterImplSkip.java
@@ -4,7 +4,7 @@
class IterImplSkip {
- static final boolean[] breaks = new boolean[256];
+ static final boolean[] breaks = new boolean[127];
static {
breaks[' '] = true;
@@ -33,10 +33,14 @@ public static final void skip(JsonIterator iter) throws IOException {
case '7':
case '8':
case '9':
+ IterImpl.skipUntilBreak(iter);
+ return;
case 't':
- case 'f':
case 'n':
- IterImpl.skipUntilBreak(iter);
+ IterImpl.skipFixedBytes(iter, 3); // true or null
+ return;
+ case 'f':
+ IterImpl.skipFixedBytes(iter, 4); // false
return;
case '[':
IterImpl.skipArray(iter);
diff --git a/src/main/java/com/jsoniter/IterImplString.java b/src/main/java/com/jsoniter/IterImplString.java
index 237bb594..c6a0b452 100644
--- a/src/main/java/com/jsoniter/IterImplString.java
+++ b/src/main/java/com/jsoniter/IterImplString.java
@@ -1,203 +1,105 @@
+/*
+this implementations contains significant code from https://github.com/ngs-doo/dsl-json/blob/master/LICENSE
+
+Copyright (c) 2015, Nova Generacija Softvera d.o.o.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of Nova Generacija Softvera d.o.o. nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.jsoniter;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import static java.lang.Character.MIN_HIGH_SURROGATE;
-import static java.lang.Character.MIN_LOW_SURROGATE;
-import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT;
-
class IterImplString {
- static int[] base64Tbl = {
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
- 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2,
- 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
- 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30,
- 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
- 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
+ final static int[] hexDigits = new int['f' + 1];
- public static final String readString(JsonIterator iter) throws IOException {
- byte c = IterImpl.nextToken(iter);
- if (c == '"') {
- // try fast path first
- for (int i = iter.head, j = 0; i < iter.tail && j < iter.reusableChars.length; i++, j++) {
- c = iter.buf[i];
- if (c == '"') {
- iter.head = i + 1;
- return new String(iter.reusableChars, 0, j);
- }
- // If we encounter a backslash, which is a beginning of an escape sequence
- // or a high bit was set - indicating an UTF-8 encoded multibyte character,
- // there is no chance that we can decode the string without instantiating
- // a temporary buffer, so quit this loop
- if ((c ^ '\\') < 1) break;
- iter.reusableChars[j] = (char) c;
- }
- return readStringSlowPath(iter);
+ static {
+ for (int i = 0; i < hexDigits.length; i++) {
+ hexDigits[i] = -1;
+ }
+ for (int i = '0'; i <= '9'; ++i) {
+ hexDigits[i] = (i - '0');
+ }
+ for (int i = 'a'; i <= 'f'; ++i) {
+ hexDigits[i] = ((i - 'a') + 10);
}
- if (c == 'n') {
- IterImpl.skipUntilBreak(iter);
- return null;
+ for (int i = 'A'; i <= 'F'; ++i) {
+ hexDigits[i] = ((i - 'A') + 10);
}
- throw iter.reportError("readString", "expect n or \"");
}
- final static String readStringSlowPath(JsonIterator iter) throws IOException {
- // http://grepcode.com/file_/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/nio/cs/UTF_8.java/?v=source
- // byte => char with support of escape in one pass
- int j = 0;
- int minimumCapacity = iter.reusableChars.length - 2;
- for (; ; ) {
- if (j == minimumCapacity) {
- char[] newBuf = new char[iter.reusableChars.length * 2];
- System.arraycopy(iter.reusableChars, 0, newBuf, 0, iter.reusableChars.length);
- iter.reusableChars = newBuf;
- minimumCapacity = iter.reusableChars.length - 2;
- }
- int b1 = IterImpl.readByte(iter);
- if (b1 >= 0) {
- if (b1 == '"') {
- return new String(iter.reusableChars, 0, j);
- } else if (b1 == '\\') {
- int b2 = IterImpl.readByte(iter);
- switch (b2) {
- case '"':
- iter.reusableChars[j++] = '"';
- break;
- case '\\':
- iter.reusableChars[j++] = '\\';
- break;
- case '/':
- iter.reusableChars[j++] = '/';
- break;
- case 'b':
- iter.reusableChars[j++] = '\b';
- break;
- case 'f':
- iter.reusableChars[j++] = '\f';
- break;
- case 'n':
- iter.reusableChars[j++] = '\n';
- break;
- case 'r':
- iter.reusableChars[j++] = '\r';
- break;
- case 't':
- iter.reusableChars[j++] = '\t';
- break;
- case 'u':
- iter.reusableChars[j++] = IterImplNumber.readU4(iter);
- break;
- default:
- throw iter.reportError("readStringSlowPath", "unexpected escape char: " + b2);
- }
- } else if (b1 == 0) {
- throw iter.reportError("readStringSlowPath", "incomplete string");
- } else {
- // 1 byte, 7 bits: 0xxxxxxx
- iter.reusableChars[j++] = (char) b1;
- }
- } else if ((b1 >> 5) == -2 && (b1 & 0x1e) != 0) {
- // 2 bytes, 11 bits: 110xxxxx 10xxxxxx
- int b2 = IterImpl.readByte(iter);
- iter.reusableChars[j++] = (char) (((b1 << 6) ^ b2)
- ^
- (((byte) 0xC0 << 6) ^
- ((byte) 0x80 << 0)));
- } else if ((b1 >> 4) == -2) {
- // 3 bytes, 16 bits: 1110xxxx 10xxxxxx 10xxxxxx
- int b2 = IterImpl.readByte(iter);
- int b3 = IterImpl.readByte(iter);
- char c = (char)
- ((b1 << 12) ^
- (b2 << 6) ^
- (b3 ^
- (((byte) 0xE0 << 12) ^
- ((byte) 0x80 << 6) ^
- ((byte) 0x80 << 0))));
- iter.reusableChars[j++] = c;
- } else if ((b1 >> 3) == -2) {
- // 4 bytes, 21 bits: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
- int b2 = IterImpl.readByte(iter);
- int b3 = IterImpl.readByte(iter);
- int b4 = IterImpl.readByte(iter);
- int uc = ((b1 << 18) ^
- (b2 << 12) ^
- (b3 << 6) ^
- (b4 ^
- (((byte) 0xF0 << 18) ^
- ((byte) 0x80 << 12) ^
- ((byte) 0x80 << 6) ^
- ((byte) 0x80 << 0))));
- iter.reusableChars[j++] = highSurrogate(uc);
- iter.reusableChars[j++] = lowSurrogate(uc);
- } else {
- throw iter.reportError("readStringSlowPath", "unexpected input");
+ public static final String readString(JsonIterator iter) throws IOException {
+ byte c = IterImpl.nextToken(iter);
+ if (c != '"') {
+ if (c == 'n') {
+ IterImpl.skipFixedBytes(iter, 3);
+ return null;
}
+ throw iter.reportError("readString", "expect string or null, but " + (char) c);
}
+ int j = parse(iter);
+ return new String(iter.reusableChars, 0, j);
}
- private static char highSurrogate(int codePoint) {
- return (char) ((codePoint >>> 10)
- + (MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT >>> 10)));
- }
-
- private static char lowSurrogate(int codePoint) {
- return (char) ((codePoint & 0x3ff) + MIN_LOW_SURROGATE);
- }
-
- public static final byte[] readBase64(JsonIterator iter) throws IOException {
- // from https://gist.github.com/EmilHernvall/953733
- Slice slice = IterImpl.readSlice(iter);
- if (slice == null) {
- return null;
- }
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- int end = slice.tail();
- for (int i = slice.head(); i < end; i++) {
- int b = 0;
- if (base64Tbl[slice.data()[i]] != -1) {
- b = (base64Tbl[slice.data()[i]] & 0xFF) << 18;
- }
- // skip unknown characters
- else {
- i++;
- continue;
- }
-
- int num = 0;
- if (i + 1 < end && base64Tbl[slice.data()[i + 1]] != -1) {
- b = b | ((base64Tbl[slice.data()[i + 1]] & 0xFF) << 12);
- num++;
- }
- if (i + 2 < end && base64Tbl[slice.data()[i + 2]] != -1) {
- b = b | ((base64Tbl[slice.data()[i + 2]] & 0xFF) << 6);
- num++;
+ private static int parse(JsonIterator iter) throws IOException {
+ byte c;// try fast path first
+ int i = iter.head;
+ // this code will trigger jvm hotspot pattern matching to highly optimized assembly
+ int bound = iter.reusableChars.length;
+ bound = IterImpl.updateStringCopyBound(iter, bound);
+ for(int j = 0; j < bound; j++) {
+ c = iter.buf[i++];
+ if (c == '"') {
+ iter.head = i;
+ return j;
}
- if (i + 3 < end && base64Tbl[slice.data()[i + 3]] != -1) {
- b = b | (base64Tbl[slice.data()[i + 3]] & 0xFF);
- num++;
+ // If we encounter a backslash, which is a beginning of an escape sequence
+ // or a high bit was set - indicating an UTF-8 encoded multibyte character,
+ // there is no chance that we can decode the string without instantiating
+ // a temporary buffer, so quit this loop
+ if ((c ^ '\\') < 1) {
+ break;
}
+ iter.reusableChars[j] = (char) c;
+ }
+ int alreadyCopied = 0;
+ if (i > iter.head) {
+ alreadyCopied = i - iter.head - 1;
+ iter.head = i - 1;
+ }
+ return IterImpl.readStringSlowPath(iter, alreadyCopied);
+ }
- while (num > 0) {
- int c = (b & 0xFF0000) >> 16;
- buffer.write((char) c);
- b <<= 8;
- num--;
- }
- i += 4;
+ public static int translateHex(final byte b) {
+ int val = hexDigits[b];
+ if (val == -1) {
+ throw new IndexOutOfBoundsException(b + " is not valid hex digit");
}
- return buffer.toByteArray();
+ return val;
}
// slice does not allow escape
diff --git a/src/main/java/com/jsoniter/JsonIterator.java b/src/main/java/com/jsoniter/JsonIterator.java
index 70dcf567..c198540b 100644
--- a/src/main/java/com/jsoniter/JsonIterator.java
+++ b/src/main/java/com/jsoniter/JsonIterator.java
@@ -1,24 +1,29 @@
package com.jsoniter;
-import com.jsoniter.annotation.JsoniterAnnotationSupport;
import com.jsoniter.any.Any;
-import com.jsoniter.spi.TypeLiteral;
+import com.jsoniter.spi.*;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
public class JsonIterator implements Closeable {
+ public Config configCache;
private static boolean isStreamingEnabled = false;
final static ValueType[] valueTypes = new ValueType[256];
InputStream in;
byte[] buf;
+ // Whenever buf is not large enough new one is created with size of
+ // buf.length + autoExpandBufferStep. Set to < 1 to disable auto expanding.
+ int autoExpandBufferStep;
int head;
int tail;
int skipStartedAt = -1; // skip should keep bytes starting at this pos
@@ -58,13 +63,22 @@ private JsonIterator(InputStream in, byte[] buf, int head, int tail) {
this.tail = tail;
}
+ private JsonIterator(InputStream in, byte[] buf, int autoExpandBufferStep) {
+ this(in, buf, 0, 0);
+ this.autoExpandBufferStep = autoExpandBufferStep;
+ }
+
public JsonIterator() {
this(null, new byte[0], 0, 0);
}
public static JsonIterator parse(InputStream in, int bufSize) {
+ return parse(in, bufSize, bufSize);
+ }
+
+ public static JsonIterator parse(InputStream in, int bufSize, int autoExpandBufferStep) {
enableStreamingSupport();
- return new JsonIterator(in, new byte[bufSize], 0, 0);
+ return new JsonIterator(in, new byte[bufSize], autoExpandBufferStep);
}
public static JsonIterator parse(byte[] buf) {
@@ -102,6 +116,7 @@ public final void reset(Slice value) {
}
public final void reset(InputStream in) {
+ JsonIterator.enableStreamingSupport();
this.in = in;
this.head = 0;
this.tail = 0;
@@ -125,7 +140,11 @@ public final JsonException reportError(String op, String msg) {
if (peekStart < 0) {
peekStart = 0;
}
- String peek = new String(buf, peekStart, head - peekStart);
+ int peekSize = head - peekStart;
+ if (head > tail) {
+ peekSize = tail - peekStart;
+ }
+ String peek = new String(buf, peekStart, peekSize);
throw new JsonException(op + ": " + msg + ", head: " + head + ", peek: " + peek + ", buf: " + new String(buf));
}
@@ -140,26 +159,25 @@ public final String currentBuffer() {
public final boolean readNull() throws IOException {
byte c = IterImpl.nextToken(this);
- if (c == 'n') {
- IterImpl.skipUntilBreak(this);
- return true;
+ if (c != 'n') {
+ unreadByte();
+ return false;
}
- unreadByte();
- return false;
+ IterImpl.skipFixedBytes(this, 3); // null
+ return true;
}
public final boolean readBoolean() throws IOException {
byte c = IterImpl.nextToken(this);
- switch (c) {
- case 't':
- IterImpl.skipUntilBreak(this);
- return true;
- case 'f':
- IterImpl.skipUntilBreak(this);
- return false;
- default:
- throw reportError("readBoolean", "expect t or f, found: " + c);
+ if ('t' == c) {
+ IterImpl.skipFixedBytes(this, 3); // true
+ return true;
}
+ if ('f' == c) {
+ IterImpl.skipFixedBytes(this, 4); // false
+ return false;
+ }
+ throw reportError("readBoolean", "expect t or f, found: " + c);
}
public final short readShort() throws IOException {
@@ -167,7 +185,7 @@ public final short readShort() throws IOException {
if (Short.MIN_VALUE <= v && v <= Short.MAX_VALUE) {
return (short) v;
} else {
- throw new JsonException("short overflow: " + v);
+ throw reportError("readShort", "short overflow: " + v);
}
}
@@ -180,67 +198,40 @@ public final long readLong() throws IOException {
}
public final boolean readArray() throws IOException {
- byte c = IterImpl.nextToken(this);
- switch (c) {
- case '[':
- c = IterImpl.nextToken(this);
- if (c == ']') {
- return false;
- } else {
- unreadByte();
- return true;
- }
- case ']':
- return false;
- case ',':
- return true;
- case 'n':
- return false;
- default:
- throw reportError("readArray", "expect [ or , or n or ], but found: " + (char) c);
- }
+ return IterImplArray.readArray(this);
+ }
+
+ public String readNumberAsString() throws IOException {
+ IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(this);
+ return new String(numberChars.chars, 0, numberChars.charsLength);
+ }
+
+ public static interface ReadArrayCallback {
+ boolean handle(JsonIterator iter, Object attachment) throws IOException;
+ }
+
+ public final boolean readArrayCB(ReadArrayCallback callback, Object attachment) throws IOException {
+ return IterImplArray.readArrayCB(this, callback, attachment);
}
public final String readString() throws IOException {
return IterImplString.readString(this);
}
- public final byte[] readBase64() throws IOException {
- return IterImplString.readBase64(this);
+ public final Slice readStringAsSlice() throws IOException {
+ return IterImpl.readSlice(this);
}
public final String readObject() throws IOException {
- byte c = IterImpl.nextToken(this);
- switch (c) {
- case 'n':
- IterImpl.skipUntilBreak(this);
- return null;
- case '{':
- c = IterImpl.nextToken(this);
- switch (c) {
- case '}':
- return null; // end of object
- case '"':
- unreadByte();
- String field = readString();
- if (IterImpl.nextToken(this) != ':') {
- throw reportError("readObject", "expect :");
- }
- return field;
- default:
- throw reportError("readObject", "expect \" after {");
- }
- case ',':
- String field = readString();
- if (IterImpl.nextToken(this) != ':') {
- throw reportError("readObject", "expect :");
- }
- return field;
- case '}':
- return null; // end of object
- default:
- throw reportError("readObject", "expect { or , or } or n");
- }
+ return IterImplObject.readObject(this);
+ }
+
+ public static interface ReadObjectCallback {
+ boolean handle(JsonIterator iter, String field, Object attachment) throws IOException;
+ }
+
+ public final void readObjectCB(ReadObjectCallback cb, Object attachment) throws IOException {
+ IterImplObject.readObjectCB(this, cb, attachment);
}
public final float readFloat() throws IOException {
@@ -252,43 +243,99 @@ public final double readDouble() throws IOException {
}
public final BigDecimal readBigDecimal() throws IOException {
- return new BigDecimal(IterImplNumber.readNumber(this));
+ // skip whitespace by read next
+ ValueType valueType = whatIsNext();
+ if (valueType == ValueType.NULL) {
+ skip();
+ return null;
+ }
+ if (valueType != ValueType.NUMBER) {
+ throw reportError("readBigDecimal", "not number");
+ }
+ IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(this);
+ return new BigDecimal(numberChars.chars, 0, numberChars.charsLength);
}
public final BigInteger readBigInteger() throws IOException {
- return new BigInteger(IterImplNumber.readNumber(this));
+ // skip whitespace by read next
+ ValueType valueType = whatIsNext();
+ if (valueType == ValueType.NULL) {
+ skip();
+ return null;
+ }
+ if (valueType != ValueType.NUMBER) {
+ throw reportError("readBigDecimal", "not number");
+ }
+ IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(this);
+ return new BigInteger(new String(numberChars.chars, 0, numberChars.charsLength));
}
public final Any readAny() throws IOException {
- return IterImpl.readAny(this);
+ try {
+ return IterImpl.readAny(this);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw reportError("read", "premature end");
+ }
}
+ private final static ReadArrayCallback fillArray = new ReadArrayCallback() {
+ @Override
+ public boolean handle(JsonIterator iter, Object attachment) throws IOException {
+ List list = (List) attachment;
+ list.add(iter.read());
+ return true;
+ }
+ };
+
+ private final static ReadObjectCallback fillObject = new ReadObjectCallback() {
+ @Override
+ public boolean handle(JsonIterator iter, String field, Object attachment) throws IOException {
+ Map map = (Map) attachment;
+ map.put(field, iter.read());
+ return true;
+ }
+ };
+
public final Object read() throws IOException {
- ValueType valueType = whatIsNext();
- switch (valueType) {
- case STRING:
- return readString();
- case NUMBER:
- return readDouble();
- case NULL:
- IterImpl.skipUntilBreak(this);
- return null;
- case BOOLEAN:
- return readBoolean();
- case ARRAY:
- ArrayList list = new ArrayList();
- while (readArray()) {
- list.add(read());
- }
- return list;
- case OBJECT:
- Map map = new HashMap();
- for (String field = readObject(); field != null; field = readObject()) {
- map.put(field, read());
- }
- return map;
- default:
- throw reportError("read", "unexpected value type: " + valueType);
+ try {
+ ValueType valueType = whatIsNext();
+ switch (valueType) {
+ case STRING:
+ return readString();
+ case NUMBER:
+ IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(this);
+ String numberStr = new String(numberChars.chars, 0, numberChars.charsLength);
+ Double number = Double.valueOf(numberStr);
+ if (numberChars.dotFound) {
+ return number;
+ }
+ double doubleNumber = number;
+ if (doubleNumber == Math.floor(doubleNumber) && !Double.isInfinite(doubleNumber)) {
+ long longNumber = Long.valueOf(numberStr);
+ if (longNumber <= Integer.MAX_VALUE && longNumber >= Integer.MIN_VALUE) {
+ return (int) longNumber;
+ }
+ return longNumber;
+ }
+ return number;
+ case NULL:
+ IterImpl.skipFixedBytes(this, 4);
+ return null;
+ case BOOLEAN:
+ return readBoolean();
+ case ARRAY:
+ ArrayList list = new ArrayList(4);
+ readArrayCB(fillArray, list);
+ return list;
+ case OBJECT:
+ Map map = new HashMap(4);
+ readObjectCB(fillObject, map);
+ return map;
+ default:
+ throw reportError("read", "unexpected value type: " + valueType);
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw reportError("read", "premature end");
}
}
@@ -296,37 +343,62 @@ public final Object read() throws IOException {
* try to bind to existing object, returned object might not the same instance
*
* @param existingObject the object instance to reuse
- * @param object type
+ * @param object type
* @return data binding result, might not be the same object
* @throws IOException if I/O went wrong
*/
public final T read(T existingObject) throws IOException {
- this.existingObject = existingObject;
- Class> clazz = existingObject.getClass();
- return (T) Codegen.getDecoder(TypeLiteral.create(clazz).getDecoderCacheKey(), clazz).decode(this);
+ try {
+ this.existingObject = existingObject;
+ Class> clazz = existingObject.getClass();
+ String cacheKey = currentConfig().getDecoderCacheKey(clazz);
+ return (T) Codegen.getDecoder(cacheKey, clazz).decode(this);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw reportError("read", "premature end");
+ }
+ }
+
+ private Config currentConfig() {
+ if (configCache == null) {
+ configCache = JsoniterSpi.getCurrentConfig();
+ }
+ return configCache;
}
/**
* try to bind to existing object, returned object might not the same instance
*
- * @param typeLiteral the type object
+ * @param typeLiteral the type object
* @param existingObject the object instance to reuse
- * @param object type
+ * @param object type
* @return data binding result, might not be the same object
* @throws IOException if I/O went wrong
*/
public final T read(TypeLiteral typeLiteral, T existingObject) throws IOException {
- this.existingObject = existingObject;
- return (T) Codegen.getDecoder(typeLiteral.getDecoderCacheKey(), typeLiteral.getType()).decode(this);
+ try {
+ this.existingObject = existingObject;
+ String cacheKey = currentConfig().getDecoderCacheKey(typeLiteral.getType());
+ return (T) Codegen.getDecoder(cacheKey, typeLiteral.getType()).decode(this);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw reportError("read", "premature end");
+ }
}
public final T read(Class clazz) throws IOException {
- return (T) Codegen.getDecoder(TypeLiteral.create(clazz).getDecoderCacheKey(), clazz).decode(this);
+ return (T) read((Type) clazz);
}
public final T read(TypeLiteral typeLiteral) throws IOException {
- String cacheKey = typeLiteral.getDecoderCacheKey();
- return (T) Codegen.getDecoder(cacheKey, typeLiteral.getType()).decode(this);
+ return (T) read(typeLiteral.getType());
+ }
+
+ public final Object read(Type type) throws IOException {
+ try {
+ String cacheKey = currentConfig().getDecoderCacheKey(type);
+ return Codegen.getDecoder(cacheKey, type).decode(this);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw reportError("read", "premature end");
+ }
}
public ValueType whatIsNext() throws IOException {
@@ -339,66 +411,94 @@ public void skip() throws IOException {
IterImplSkip.skip(this);
}
- private static ThreadLocal tlsIter = new ThreadLocal() {
- @Override
- protected JsonIterator initialValue() {
- return new JsonIterator();
+ public static final T deserialize(Config config, String input, Class clazz) {
+ JsoniterSpi.setCurrentConfig(config);
+ try {
+ return deserialize(input.getBytes(), clazz);
+ } finally {
+ JsoniterSpi.clearCurrentConfig();
}
- };
+ }
public static final T deserialize(String input, Class clazz) {
- JsonIterator iter = tlsIter.get();
- iter.reset(input.getBytes());
+ return deserialize(input.getBytes(), clazz);
+ }
+
+ public static final T deserialize(Config config, String input, TypeLiteral typeLiteral) {
+ JsoniterSpi.setCurrentConfig(config);
try {
- T val = iter.read(clazz);
- if (IterImpl.nextToken(iter) != 0) {
- throw iter.reportError("deserialize", "trailing garbage found");
- }
- return val;
- } catch (IOException e) {
- throw new JsonException(e);
+ return deserialize(input.getBytes(), typeLiteral);
+ } finally {
+ JsoniterSpi.clearCurrentConfig();
}
}
public static final T deserialize(String input, TypeLiteral typeLiteral) {
- JsonIterator iter = tlsIter.get();
- iter.reset(input.getBytes());
+ return deserialize(input.getBytes(), typeLiteral);
+ }
+
+ public static final T deserialize(Config config, byte[] input, Class clazz) {
+ JsoniterSpi.setCurrentConfig(config);
try {
- T val = iter.read(typeLiteral);
- if (IterImpl.nextToken(iter) != 0) {
- throw iter.reportError("deserialize", "trailing garbage found");
- }
- return val;
- } catch (IOException e) {
- throw new JsonException(e);
+ return deserialize(input, clazz);
+ } finally {
+ JsoniterSpi.clearCurrentConfig();
}
}
public static final T deserialize(byte[] input, Class clazz) {
- JsonIterator iter = tlsIter.get();
- iter.reset(input);
+ int lastNotSpacePos = findLastNotSpacePos(input);
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
+ iter.reset(input, 0, lastNotSpacePos);
try {
T val = iter.read(clazz);
- if (IterImpl.nextToken(iter) != 0) {
+ if (iter.head != lastNotSpacePos) {
throw iter.reportError("deserialize", "trailing garbage found");
}
return val;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw iter.reportError("deserialize", "premature end");
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
+ }
+ }
+
+ public static final T deserialize(Config config, byte[] input, TypeLiteral typeLiteral) {
+ JsoniterSpi.setCurrentConfig(config);
+ try {
+ return deserialize(input, typeLiteral);
+ } finally {
+ JsoniterSpi.clearCurrentConfig();
}
}
public static final T deserialize(byte[] input, TypeLiteral typeLiteral) {
- JsonIterator iter = tlsIter.get();
- iter.reset(input);
+ int lastNotSpacePos = findLastNotSpacePos(input);
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
+ iter.reset(input, 0, lastNotSpacePos);
try {
T val = iter.read(typeLiteral);
- if (IterImpl.nextToken(iter) != 0) {
+ if (iter.head != lastNotSpacePos) {
throw iter.reportError("deserialize", "trailing garbage found");
}
return val;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw iter.reportError("deserialize", "premature end");
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
+ }
+ }
+
+ public static final Any deserialize(Config config, String input) {
+ JsoniterSpi.setCurrentConfig(config);
+ try {
+ return deserialize(input.getBytes());
+ } finally {
+ JsoniterSpi.clearCurrentConfig();
}
}
@@ -406,22 +506,48 @@ public static final Any deserialize(String input) {
return deserialize(input.getBytes());
}
+ public static final Any deserialize(Config config, byte[] input) {
+ JsoniterSpi.setCurrentConfig(config);
+ try {
+ return deserialize(input);
+ } finally {
+ JsoniterSpi.clearCurrentConfig();
+ }
+ }
+
public static final Any deserialize(byte[] input) {
- JsonIterator iter = tlsIter.get();
- iter.reset(input);
+ int lastNotSpacePos = findLastNotSpacePos(input);
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
+ iter.reset(input, 0, lastNotSpacePos);
try {
Any val = iter.readAny();
- if (IterImpl.nextToken(iter) != 0) {
+ if (iter.head != lastNotSpacePos) {
throw iter.reportError("deserialize", "trailing garbage found");
}
return val;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw iter.reportError("deserialize", "premature end");
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
+ private static int findLastNotSpacePos(byte[] input) {
+ for (int i = input.length - 1; i >= 0; i--) {
+ byte c = input[i];
+ if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
+ return i + 1;
+ }
+ }
+ return 0;
+ }
+
public static void setMode(DecodingMode mode) {
- Codegen.setMode(mode);
+ Config newConfig = JsoniterSpi.getDefaultConfig().copyBuilder().decodingMode(mode).build();
+ JsoniterSpi.setDefaultConfig(newConfig);
+ JsoniterSpi.setCurrentConfig(newConfig);
}
public static void enableStreamingSupport() {
@@ -431,12 +557,10 @@ public static void enableStreamingSupport() {
isStreamingEnabled = true;
try {
DynamicCodegen.enableStreamingSupport();
+ } catch (JsonException e) {
+ throw e;
} catch (Exception e) {
throw new JsonException(e);
}
}
-
- public static void enableAnnotationSupport() {
- JsoniterAnnotationSupport.enable();
- }
}
diff --git a/src/main/java/com/jsoniter/JsonIteratorPool.java b/src/main/java/com/jsoniter/JsonIteratorPool.java
new file mode 100644
index 00000000..f0324d02
--- /dev/null
+++ b/src/main/java/com/jsoniter/JsonIteratorPool.java
@@ -0,0 +1,35 @@
+package com.jsoniter;
+
+public class JsonIteratorPool {
+
+ private static ThreadLocal slot1 = new ThreadLocal();
+ private static ThreadLocal slot2 = new ThreadLocal();
+
+ public static JsonIterator borrowJsonIterator() {
+ JsonIterator iter = slot1.get();
+ if (iter != null) {
+ slot1.set(null);
+ return iter;
+ }
+ iter = slot2.get();
+ if (iter != null) {
+ slot2.set(null);
+ return iter;
+ }
+ iter = JsonIterator.parse(new byte[512], 0, 0);
+ return iter;
+ }
+
+ public static void returnJsonIterator(JsonIterator iter) {
+ iter.configCache = null;
+ iter.existingObject = null;
+ if (slot1.get() == null) {
+ slot1.set(iter);
+ return;
+ }
+ if (slot2.get() == null) {
+ slot2.set(iter);
+ return;
+ }
+ }
+}
diff --git a/src/main/java/com/jsoniter/MapKeyDecoders.java b/src/main/java/com/jsoniter/MapKeyDecoders.java
new file mode 100644
index 00000000..0bb75a30
--- /dev/null
+++ b/src/main/java/com/jsoniter/MapKeyDecoders.java
@@ -0,0 +1,77 @@
+package com.jsoniter;
+
+import com.jsoniter.spi.*;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+
+class MapKeyDecoders {
+
+ public static Decoder registerOrGetExisting(Type mapKeyType) {
+ String cacheKey = JsoniterSpi.getMapKeyDecoderCacheKey(mapKeyType);
+ Decoder mapKeyDecoder = JsoniterSpi.getMapKeyDecoder(cacheKey);
+ if (null != mapKeyDecoder) {
+ return mapKeyDecoder;
+ }
+ mapKeyDecoder = createMapKeyDecoder(mapKeyType);
+ JsoniterSpi.addNewMapDecoder(cacheKey, mapKeyDecoder);
+ return mapKeyDecoder;
+ }
+
+ private static Decoder createMapKeyDecoder(Type mapKeyType) {
+ if (String.class == mapKeyType) {
+ return new StringKeyDecoder();
+ }
+ if (mapKeyType instanceof Class && ((Class) mapKeyType).isEnum()) {
+ return new EnumKeyDecoder((Class) mapKeyType);
+ }
+ Decoder decoder = CodegenImplNative.NATIVE_DECODERS.get(mapKeyType);
+ if (decoder != null) {
+ return new NumberKeyDecoder(decoder);
+ }
+ throw new JsonException("can not decode map key type: " + mapKeyType);
+ }
+
+ private static class StringKeyDecoder implements Decoder {
+
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.readString();
+ }
+ }
+
+ private static class EnumKeyDecoder implements Decoder {
+
+ private final Class enumClass;
+
+ private EnumKeyDecoder(Class enumClass) {
+ this.enumClass = enumClass;
+ }
+
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ return iter.read(enumClass);
+ }
+ }
+
+ private static class NumberKeyDecoder implements Decoder {
+
+ private final Decoder decoder;
+
+ private NumberKeyDecoder(Decoder decoder) {
+ this.decoder = decoder;
+ }
+
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ if (IterImpl.nextToken(iter) != '"') {
+ throw iter.reportError("decode number map key", "expect \"");
+ }
+ Object key = decoder.decode(iter);
+ if (IterImpl.nextToken(iter) != '"') {
+ throw iter.reportError("decode number map key", "expect \"");
+ }
+ return key;
+ }
+ }
+}
diff --git a/src/main/java/com/jsoniter/README.md b/src/main/java/com/jsoniter/README.md
new file mode 100644
index 00000000..422ab7e4
--- /dev/null
+++ b/src/main/java/com/jsoniter/README.md
@@ -0,0 +1,17 @@
+there are 7 packages, listed in abstraction level order
+
+# bottom abstraction
+
+* spi: bottom of the abstraction
+
+# concrete implementations
+
+* iterator/any: these two packages are tangled together, doing decoding
+* output: doing encoding, should only depend on spi
+
+# addons
+
+* annotation: make spi accessible with annotation. everything here can be done using code
+* fuzzy: pre-defined decoders to work with messy input
+* extra: extra encoders/decoders, useful for a lot of people, but not all of them
+* static_codegen: command to generate encoder/decoder statically
\ No newline at end of file
diff --git a/src/main/java/com/jsoniter/ReflectionArrayDecoder.java b/src/main/java/com/jsoniter/ReflectionArrayDecoder.java
index da01e882..9a151edd 100644
--- a/src/main/java/com/jsoniter/ReflectionArrayDecoder.java
+++ b/src/main/java/com/jsoniter/ReflectionArrayDecoder.java
@@ -1,6 +1,7 @@
package com.jsoniter;
import com.jsoniter.spi.Decoder;
+import com.jsoniter.spi.JsoniterSpi;
import com.jsoniter.spi.TypeLiteral;
import java.io.IOException;
@@ -9,11 +10,11 @@
class ReflectionArrayDecoder implements Decoder {
private final Class componentType;
- private final TypeLiteral compTypeLiteral;
+ private final Decoder compTypeDecoder;
public ReflectionArrayDecoder(Class clazz) {
componentType = clazz.getComponentType();
- compTypeLiteral = TypeLiteral.create(componentType);
+ compTypeDecoder = Codegen.getDecoder(TypeLiteral.create(componentType).getDecoderCacheKey(), componentType);
}
@Override
@@ -25,20 +26,20 @@ public Object decode(JsonIterator iter) throws IOException {
if (!CodegenAccess.readArrayStart(iter)) {
return Array.newInstance(componentType, 0);
}
- Object a1 = CodegenAccess.read(iter, compTypeLiteral);
+ Object a1 = compTypeDecoder.decode(iter);
if (CodegenAccess.nextToken(iter) != ',') {
Object arr = Array.newInstance(componentType, 1);
Array.set(arr, 0, a1);
return arr;
}
- Object a2 = CodegenAccess.read(iter, compTypeLiteral);
+ Object a2 = compTypeDecoder.decode(iter);
if (CodegenAccess.nextToken(iter) != ',') {
Object arr = Array.newInstance(componentType, 2);
Array.set(arr, 0, a1);
Array.set(arr, 1, a2);
return arr;
}
- Object a3 = CodegenAccess.read(iter, compTypeLiteral);
+ Object a3 = compTypeDecoder.decode(iter);
if (CodegenAccess.nextToken(iter) != ',') {
Object arr = Array.newInstance(componentType, 3);
Array.set(arr, 0, a1);
@@ -46,7 +47,7 @@ public Object decode(JsonIterator iter) throws IOException {
Array.set(arr, 2, a3);
return arr;
}
- Object a4 = CodegenAccess.read(iter, compTypeLiteral);
+ Object a4 = compTypeDecoder.decode(iter);
Object arr = Array.newInstance(componentType, 8);
Array.set(arr, 0, a1);
Array.set(arr, 1, a2);
@@ -61,7 +62,7 @@ public Object decode(JsonIterator iter) throws IOException {
arr = newArr;
arrLen = 2 * arrLen;
}
- Array.set(arr, i++, CodegenAccess.read(iter, compTypeLiteral));
+ Array.set(arr, i++, compTypeDecoder.decode(iter));
}
if (i == arrLen) {
return arr;
diff --git a/src/main/java/com/jsoniter/ReflectionCollectionDecoder.java b/src/main/java/com/jsoniter/ReflectionCollectionDecoder.java
index 41a052d3..b82dfe91 100644
--- a/src/main/java/com/jsoniter/ReflectionCollectionDecoder.java
+++ b/src/main/java/com/jsoniter/ReflectionCollectionDecoder.java
@@ -1,6 +1,7 @@
package com.jsoniter;
import com.jsoniter.spi.Decoder;
+import com.jsoniter.spi.JsonException;
import com.jsoniter.spi.TypeLiteral;
import java.io.IOException;
@@ -9,8 +10,8 @@
import java.util.Collection;
class ReflectionCollectionDecoder implements Decoder {
- private final TypeLiteral compTypeLiteral;
private final Constructor ctor;
+ private final Decoder compTypeDecoder;
public ReflectionCollectionDecoder(Class clazz, Type[] typeArgs) {
try {
@@ -18,13 +19,15 @@ public ReflectionCollectionDecoder(Class clazz, Type[] typeArgs) {
} catch (NoSuchMethodException e) {
throw new JsonException(e);
}
- compTypeLiteral = TypeLiteral.create(typeArgs[0]);
+ compTypeDecoder = Codegen.getDecoder(TypeLiteral.create(typeArgs[0]).getDecoderCacheKey(), typeArgs[0]);
}
@Override
public Object decode(JsonIterator iter) throws IOException {
try {
return decode_(iter);
+ } catch (JsonException e) {
+ throw e;
} catch (Exception e) {
throw new JsonException(e);
}
@@ -41,7 +44,7 @@ private Object decode_(JsonIterator iter) throws Exception {
col.clear();
}
while (iter.readArray()) {
- col.add(CodegenAccess.read(iter, compTypeLiteral));
+ col.add(compTypeDecoder.decode(iter));
}
return col;
}
diff --git a/src/main/java/com/jsoniter/ReflectionDecoderFactory.java b/src/main/java/com/jsoniter/ReflectionDecoderFactory.java
index 89565abd..65dee380 100644
--- a/src/main/java/com/jsoniter/ReflectionDecoderFactory.java
+++ b/src/main/java/com/jsoniter/ReflectionDecoderFactory.java
@@ -1,25 +1,16 @@
package com.jsoniter;
+import com.jsoniter.spi.ClassInfo;
import com.jsoniter.spi.Decoder;
-import com.jsoniter.spi.TypeLiteral;
-import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
-public class ReflectionDecoderFactory {
- public static Decoder create(Class clazz, Type... typeArgs) {
- final TypeLiteral typeLiteral = TypeLiteral.create(clazz);
- TypeLiteral.NativeType nativeType = typeLiteral.getNativeType();
- if (nativeType != null) {
- return new Decoder() {
- @Override
- public Object decode(JsonIterator iter) throws IOException {
- return CodegenAccess.read(iter, typeLiteral);
- }
- };
- }
+class ReflectionDecoderFactory {
+ public static Decoder create(ClassInfo classAndArgs) {
+ Class clazz = classAndArgs.clazz;
+ Type[] typeArgs = classAndArgs.typeArgs;
if (clazz.isArray()) {
return new ReflectionArrayDecoder(clazz);
}
@@ -32,6 +23,6 @@ public Object decode(JsonIterator iter) throws IOException {
if (clazz.isEnum()) {
return new ReflectionEnumDecoder(clazz);
}
- return new ReflectionObjectDecoder(clazz).create();
+ return new ReflectionObjectDecoder(classAndArgs).create();
}
}
diff --git a/src/main/java/com/jsoniter/ReflectionEnumDecoder.java b/src/main/java/com/jsoniter/ReflectionEnumDecoder.java
index d2a089e5..7667e9f4 100644
--- a/src/main/java/com/jsoniter/ReflectionEnumDecoder.java
+++ b/src/main/java/com/jsoniter/ReflectionEnumDecoder.java
@@ -1,6 +1,7 @@
package com.jsoniter;
import com.jsoniter.spi.Decoder;
+import com.jsoniter.spi.Slice;
import java.io.IOException;
import java.util.HashMap;
diff --git a/src/main/java/com/jsoniter/ReflectionMapDecoder.java b/src/main/java/com/jsoniter/ReflectionMapDecoder.java
index 77a93d61..7e5f220a 100644
--- a/src/main/java/com/jsoniter/ReflectionMapDecoder.java
+++ b/src/main/java/com/jsoniter/ReflectionMapDecoder.java
@@ -1,7 +1,6 @@
package com.jsoniter;
-import com.jsoniter.spi.Decoder;
-import com.jsoniter.spi.TypeLiteral;
+import com.jsoniter.spi.*;
import java.io.IOException;
import java.lang.reflect.Constructor;
@@ -11,7 +10,8 @@
class ReflectionMapDecoder implements Decoder {
private final Constructor ctor;
- private final TypeLiteral valueTypeLiteral;
+ private final Decoder valueTypeDecoder;
+ private final Decoder mapKeyDecoder;
public ReflectionMapDecoder(Class clazz, Type[] typeArgs) {
try {
@@ -19,13 +19,18 @@ public ReflectionMapDecoder(Class clazz, Type[] typeArgs) {
} catch (NoSuchMethodException e) {
throw new JsonException(e);
}
- valueTypeLiteral = TypeLiteral.create(typeArgs[1]);
+ Type keyType = typeArgs[0];
+ mapKeyDecoder = MapKeyDecoders.registerOrGetExisting(keyType);
+ TypeLiteral valueTypeLiteral = TypeLiteral.create(typeArgs[1]);
+ valueTypeDecoder = Codegen.getDecoder(valueTypeLiteral.getDecoderCacheKey(), typeArgs[1]);
}
@Override
public Object decode(JsonIterator iter) throws IOException {
try {
return decode_(iter);
+ } catch (JsonException e) {
+ throw e;
} catch (Exception e) {
throw new JsonException(e);
}
@@ -42,12 +47,18 @@ private Object decode_(JsonIterator iter) throws Exception {
if (!CodegenAccess.readObjectStart(iter)) {
return map;
}
- String field = CodegenAccess.readObjectFieldAsString(iter);
- map.put(field, CodegenAccess.read(iter, valueTypeLiteral));
- while (CodegenAccess.nextToken(iter) == ',') {
- field = CodegenAccess.readObjectFieldAsString(iter);
- map.put(field, CodegenAccess.read(iter, valueTypeLiteral));
- }
+ do {
+ Object decodedMapKey = readMapKey(iter);
+ map.put(decodedMapKey, valueTypeDecoder.decode(iter));
+ } while(CodegenAccess.nextToken(iter) == ',');
return map;
}
+
+ private Object readMapKey(JsonIterator iter) throws IOException {
+ Object key = mapKeyDecoder.decode(iter);
+ if (':' != IterImpl.nextToken(iter)) {
+ throw iter.reportError("readMapKey", "expect :");
+ }
+ return key;
+ }
}
diff --git a/src/main/java/com/jsoniter/ReflectionObjectDecoder.java b/src/main/java/com/jsoniter/ReflectionObjectDecoder.java
index 8f863d84..e1e76f73 100644
--- a/src/main/java/com/jsoniter/ReflectionObjectDecoder.java
+++ b/src/main/java/com/jsoniter/ReflectionObjectDecoder.java
@@ -1,8 +1,10 @@
package com.jsoniter;
+import com.jsoniter.any.Any;
import com.jsoniter.spi.*;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.util.*;
class ReflectionObjectDecoder {
@@ -22,46 +24,52 @@ public String toString() {
private int tempIdx;
private ClassDescriptor desc;
- public ReflectionObjectDecoder(Class clazz) {
+ public ReflectionObjectDecoder(ClassInfo classInfo) {
try {
- init(clazz);
+ init(classInfo);
+ } catch (JsonException e) {
+ throw e;
} catch (Exception e) {
throw new JsonException(e);
}
}
- private final void init(Class clazz) throws Exception {
- ClassDescriptor desc = JsoniterSpi.getDecodingClassDescriptor(clazz, true);
+ private final void init(ClassInfo classInfo) throws Exception {
+ Class clazz = classInfo.clazz;
+ ClassDescriptor desc = ClassDescriptor.getDecodingClassDescriptor(classInfo, true);
for (Binding param : desc.ctor.parameters) {
- addBinding(clazz, param);
+ addBinding(classInfo, param);
}
this.desc = desc;
if (desc.ctor.objectFactory == null && desc.ctor.ctor == null && desc.ctor.staticFactory == null) {
throw new JsonException("no constructor for: " + desc.clazz);
}
for (Binding field : desc.fields) {
- addBinding(clazz, field);
+ addBinding(classInfo, field);
}
for (Binding setter : desc.setters) {
- addBinding(clazz, setter);
+ addBinding(classInfo, setter);
}
- for (WrapperDescriptor setter : desc.wrappers) {
+ for (WrapperDescriptor setter : desc.bindingTypeWrappers) {
for (Binding param : setter.parameters) {
- addBinding(clazz, param);
+ addBinding(classInfo, param);
}
}
if (requiredIdx > 63) {
throw new JsonException("too many required properties to track");
}
expectedTracker = Long.MAX_VALUE >> (63 - requiredIdx);
- if (!desc.ctor.parameters.isEmpty() || !desc.wrappers.isEmpty()) {
+ if (!desc.ctor.parameters.isEmpty() || !desc.bindingTypeWrappers.isEmpty()) {
tempCount = tempIdx;
tempCacheKey = "temp@" + clazz.getCanonicalName();
ctorArgsCacheKey = "ctor@" + clazz.getCanonicalName();
}
}
- private void addBinding(Class clazz, final Binding binding) {
+ private void addBinding(ClassInfo classInfo, final Binding binding) {
+ if (binding.fromNames.length == 0) {
+ return;
+ }
if (binding.asMissingWhenNotPresent) {
binding.mask = 1L << requiredIdx;
requiredIdx++;
@@ -75,14 +83,17 @@ public Object decode(JsonIterator iter) throws IOException {
};
}
if (binding.decoder == null) {
- // the field decoder might be registered directly
+ // field decoder might be special customized
binding.decoder = JsoniterSpi.getDecoder(binding.decoderCacheKey());
}
+ if (binding.decoder == null) {
+ binding.decoder = Codegen.getDecoder(binding.valueTypeLiteral.getDecoderCacheKey(), binding.valueType);
+ }
binding.idx = tempIdx;
for (String fromName : binding.fromNames) {
Slice slice = Slice.make(fromName);
if (allBindings.containsKey(slice)) {
- throw new JsonException("name conflict found in " + clazz + ": " + fromName);
+ throw new JsonException("name conflict found in " + classInfo.clazz + ": " + fromName);
}
allBindings.put(slice, binding);
}
@@ -91,10 +102,10 @@ public Object decode(JsonIterator iter) throws IOException {
public Decoder create() {
if (desc.ctor.parameters.isEmpty()) {
- if (desc.wrappers.isEmpty()) {
+ if (desc.bindingTypeWrappers.isEmpty()) {
return new OnlyField();
} else {
- return new WithSetter();
+ return new WithWrapper();
}
} else {
return new WithCtor();
@@ -106,6 +117,8 @@ public class OnlyField implements Decoder {
public Object decode(JsonIterator iter) throws IOException {
try {
return decode_(iter);
+ } catch (RuntimeException e) {
+ throw e;
} catch (Exception e) {
throw new JsonException(e);
}
@@ -169,6 +182,8 @@ public class WithCtor implements Decoder {
public Object decode(JsonIterator iter) throws IOException {
try {
return decode_(iter);
+ } catch (RuntimeException e) {
+ throw e;
} catch (Exception e) {
throw new JsonException(e);
}
@@ -225,7 +240,7 @@ private Object decode_(JsonIterator iter) throws Exception {
setExtra(obj, extra);
for (Binding field : desc.fields) {
Object val = temp[field.idx];
- if (val != NOT_SET) {
+ if (val != NOT_SET && field.fromNames.length > 0) {
field.field.set(obj, val);
}
}
@@ -240,12 +255,14 @@ private Object decode_(JsonIterator iter) throws Exception {
}
}
- public class WithSetter implements Decoder {
+ public class WithWrapper implements Decoder {
@Override
public Object decode(JsonIterator iter) throws IOException {
try {
return decode_(iter);
+ } catch (RuntimeException e) {
+ throw e;
} catch (Exception e) {
throw new JsonException(e);
}
@@ -330,8 +347,23 @@ private void setToBinding(Object obj, Binding binding, Object value) throws Exce
}
private void setExtra(Object obj, Map extra) throws Exception {
- if (desc.onExtraProperties != null) {
- setToBinding(obj, desc.onExtraProperties, extra);
+ if (extra == null) {
+ return;
+ }
+ if (desc.asExtraForUnknownProperties) {
+ if (desc.onExtraProperties == null) {
+ for (String fieldName : extra.keySet()) {
+ throw new JsonException("unknown property: " + fieldName);
+ }
+ } else {
+ setToBinding(obj, desc.onExtraProperties, extra);
+ }
+ }
+ for (Method wrapper : desc.keyValueTypeWrappers) {
+ for (Map.Entry entry : extra.entrySet()) {
+ Any value = (Any) entry.getValue();
+ wrapper.invoke(obj, entry.getKey(), value.object());
+ }
}
}
@@ -341,11 +373,7 @@ private boolean canNotSetDirectly(Binding binding) {
private Object decodeBinding(JsonIterator iter, Binding binding) throws Exception {
Object value;
- if (binding.decoder == null) {
- value = CodegenAccess.read(iter, binding.valueTypeLiteral);
- } else {
- value = binding.decoder.decode(iter);
- }
+ value = binding.decoder.decode(iter);
return value;
}
@@ -357,15 +385,13 @@ private Object decodeBinding(JsonIterator iter, Object obj, Binding binding) thr
}
private Map onUnknownProperty(JsonIterator iter, Slice fieldName, Map extra) throws IOException {
- if (desc.asExtraForUnknownProperties) {
- if (desc.onExtraProperties == null) {
- throw new JsonException("unknown property: " + fieldName.toString());
- } else {
- if (extra == null) {
- extra = new HashMap();
- }
- extra.put(fieldName.toString(), iter.readAny());
+ boolean shouldReadValue = desc.asExtraForUnknownProperties || !desc.keyValueTypeWrappers.isEmpty();
+ if (shouldReadValue) {
+ Any value = iter.readAny();
+ if (extra == null) {
+ extra = new HashMap();
}
+ extra.put(fieldName.toString(), value);
} else {
iter.skip();
}
@@ -384,10 +410,13 @@ private List collectMissingFields(long tracker) {
}
private void applyWrappers(Object[] temp, Object obj) throws Exception {
- for (WrapperDescriptor wrapper : desc.wrappers) {
+ for (WrapperDescriptor wrapper : desc.bindingTypeWrappers) {
Object[] args = new Object[wrapper.parameters.size()];
for (int i = 0; i < wrapper.parameters.size(); i++) {
- args[i] = temp[wrapper.parameters.get(i).idx];
+ Object arg = temp[wrapper.parameters.get(i).idx];
+ if (arg != NOT_SET) {
+ args[i] = arg;
+ }
}
wrapper.method.invoke(obj, args);
}
diff --git a/src/main/java/com/jsoniter/StaticCodeGenerator.java b/src/main/java/com/jsoniter/StaticCodeGenerator.java
deleted file mode 100644
index b16c848d..00000000
--- a/src/main/java/com/jsoniter/StaticCodeGenerator.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.jsoniter;
-
-import com.jsoniter.output.EncodingMode;
-import com.jsoniter.output.JsonStream;
-import com.jsoniter.spi.CodegenConfig;
-
-import java.io.File;
-
-public class StaticCodeGenerator {
- public static void main(String[] args) throws Exception {
- String configClassName = args[0];
- Class> clazz = Class.forName(configClassName);
- CodegenConfig config = (CodegenConfig) clazz.newInstance();
- JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH);
- JsonStream.setMode(EncodingMode.DYNAMIC_MODE);
- config.setup();
- CodegenAccess.staticGenDecoders(config.whatToCodegen());
- com.jsoniter.output.CodegenAccess.staticGenEncoders(config.whatToCodegen());
- String configJavaFile = configClassName.replace('.', '/') + ".java";
- if (!new File(configJavaFile).exists()) {
- throw new JsonException("must execute static code generator in the java source code directory which contains: " + configJavaFile);
- }
- }
-}
diff --git a/src/main/java/com/jsoniter/annotation/JsonIgnore.java b/src/main/java/com/jsoniter/annotation/JsonIgnore.java
index 68d19207..bcb04cd2 100644
--- a/src/main/java/com/jsoniter/annotation/JsonIgnore.java
+++ b/src/main/java/com/jsoniter/annotation/JsonIgnore.java
@@ -8,5 +8,6 @@
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonIgnore {
- boolean value() default true;
+ boolean ignoreDecoding() default true;
+ boolean ignoreEncoding() default true;
}
diff --git a/src/main/java/com/jsoniter/annotation/JsonObject.java b/src/main/java/com/jsoniter/annotation/JsonObject.java
index f23f3d89..ad3d9742 100644
--- a/src/main/java/com/jsoniter/annotation/JsonObject.java
+++ b/src/main/java/com/jsoniter/annotation/JsonObject.java
@@ -9,14 +9,20 @@
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonObject {
- // if the unknown property is in this list, it will be treated as extra
- // if @JsonExtraProperties not defined, it will be treated as error
+ /**
+ * @return if the unknown property is in this list, it will be treated as extra,
+ * if the unknown property is in this list, it will be treated as extra
+ */
String[] unknownPropertiesBlacklist() default {};
- // if the unknown property is in this list, it will be silently ignored
+ /**
+ * @return if the unknown property is in this list, it will be silently ignored
+ */
String[] unknownPropertiesWhitelist() default {};
- // if true, all known properties will be treated as extra
- // if @JsonExtraProperties not defined, it will be treated as error
+ /**
+ * @return if true, all known properties will be treated as extra,
+ * if @JsonExtraProperties not defined, it will be treated as error
+ */
boolean asExtraForUnknownProperties() default false;
}
diff --git a/src/main/java/com/jsoniter/annotation/JsonProperty.java b/src/main/java/com/jsoniter/annotation/JsonProperty.java
index 75e8ebad..2bd545e5 100644
--- a/src/main/java/com/jsoniter/annotation/JsonProperty.java
+++ b/src/main/java/com/jsoniter/annotation/JsonProperty.java
@@ -12,17 +12,59 @@
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonProperty {
+ /**
+ * @return alternative name for the field/getter/setter/parameter
+ */
String value() default "";
+ /**
+ * @return when bind from multiple possible names, set this
+ */
String[] from() default {};
+ /**
+ * @return when one field will write to multiple object fields, set this
+ */
String[] to() default {};
+ /**
+ * @return used in decoding only, the field must present in the JSON, regardless null or not
+ */
boolean required() default false;
+ /**
+ * @return set different decoder just for this field
+ */
Class extends Decoder> decoder() default Decoder.class;
+ /**
+ * @return used in decoding only, choose concrete class for interface/abstract type
+ */
Class> implementation() default Object.class;
+ /**
+ * @return set different encoder just for this field
+ */
Class extends Encoder> encoder() default Encoder.class;
+
+ /**
+ * @return used in encoding only, should check null for this field,
+ * skip null checking will make encoding faster
+ */
+ boolean nullable() default true;
+
+ /**
+ * @return used in encoding only, should check null for the value, if it is collection,
+ * skip null checking will make encoding faster
+ */
+ boolean collectionValueNullable() default true;
+
+ /**
+ * @return the default value to omit
+ * null, to omit null value
+ * \"xxx\", to omit string value
+ * 123, to omit number
+ * void, to always encode this field, ignore global config
+ */
+ String defaultValueToOmit() default "";
}
diff --git a/src/main/java/com/jsoniter/annotation/JsonWrapper.java b/src/main/java/com/jsoniter/annotation/JsonWrapper.java
index 0749ab7b..7fdb4e87 100644
--- a/src/main/java/com/jsoniter/annotation/JsonWrapper.java
+++ b/src/main/java/com/jsoniter/annotation/JsonWrapper.java
@@ -8,4 +8,5 @@
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonWrapper {
+ JsonWrapperType value() default JsonWrapperType.BINDING;
}
diff --git a/src/main/java/com/jsoniter/annotation/JsonWrapperType.java b/src/main/java/com/jsoniter/annotation/JsonWrapperType.java
new file mode 100644
index 00000000..011c1a26
--- /dev/null
+++ b/src/main/java/com/jsoniter/annotation/JsonWrapperType.java
@@ -0,0 +1,6 @@
+package com.jsoniter.annotation;
+
+public enum JsonWrapperType {
+ BINDING,
+ KEY_VALUE
+}
diff --git a/src/main/java/com/jsoniter/annotation/JsoniterAnnotationSupport.java b/src/main/java/com/jsoniter/annotation/JsoniterAnnotationSupport.java
deleted file mode 100644
index bf4a79f5..00000000
--- a/src/main/java/com/jsoniter/annotation/JsoniterAnnotationSupport.java
+++ /dev/null
@@ -1,261 +0,0 @@
-package com.jsoniter.annotation;
-
-import com.jsoniter.JsonException;
-import com.jsoniter.spi.*;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.*;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class JsoniterAnnotationSupport extends EmptyExtension {
-
- private static boolean enabled = false;
-
- public static void enable() {
- if (enabled) {
- return;
- }
- enabled = true;
- JsoniterSpi.registerExtension(new JsoniterAnnotationSupport());
- }
-
- @Override
- public void updateClassDescriptor(ClassDescriptor desc) {
- JsonObject jsonObject = (JsonObject) desc.clazz.getAnnotation(JsonObject.class);
- if (jsonObject != null) {
- if (jsonObject.asExtraForUnknownProperties()) {
- desc.asExtraForUnknownProperties = true;
- }
- for (String fieldName : jsonObject.unknownPropertiesWhitelist()) {
- Binding binding = new Binding(desc.clazz, desc.lookup, Object.class);
- binding.name = fieldName;
- binding.shouldSkip = true;
- desc.fields.add(binding);
- }
- for (String fieldName : jsonObject.unknownPropertiesBlacklist()) {
- Binding binding = new Binding(desc.clazz, desc.lookup, Object.class);
- binding.name = fieldName;
- binding.asExtraWhenPresent = true;
- desc.fields.add(binding);
- }
- }
- List allMethods = new ArrayList();
- Class current = desc.clazz;
- while (current != null) {
- allMethods.addAll(Arrays.asList(current.getDeclaredMethods()));
- current = current.getSuperclass();
- }
- updateBindings(desc);
- detectCtor(desc);
- detectStaticFactory(desc, allMethods);
- detectWrappers(desc, allMethods);
- detectUnwrappers(desc, allMethods);
- }
-
- private void detectUnwrappers(ClassDescriptor desc, List allMethods) {
- for (Method method : allMethods) {
- if (Modifier.isStatic(method.getModifiers())) {
- continue;
- }
- if (method.getAnnotation(JsonUnwrapper.class) == null) {
- continue;
- }
- desc.unwrappers.add(method);
- }
- }
-
- private void detectWrappers(ClassDescriptor desc, List allMethods) {
- for (Method method : allMethods) {
- if (Modifier.isStatic(method.getModifiers())) {
- continue;
- }
- if (method.getAnnotation(JsonWrapper.class) == null) {
- continue;
- }
- Annotation[][] annotations = method.getParameterAnnotations();
- String[] paramNames = getParamNames(method, annotations.length);
- WrapperDescriptor setter = new WrapperDescriptor();
- setter.method = method;
- for (int i = 0; i < annotations.length; i++) {
- Annotation[] paramAnnotations = annotations[i];
- Binding binding = new Binding(desc.clazz, desc.lookup, method.getGenericParameterTypes()[i]);
- JsonProperty jsonProperty = getJsonProperty(paramAnnotations);
- if (jsonProperty != null) {
- binding.name = jsonProperty.value();
- if (jsonProperty.required()) {
- binding.asMissingWhenNotPresent = true;
- }
- }
- if (binding.name == null || binding.name.length() == 0) {
- binding.name = paramNames[i];
- }
- binding.annotations = paramAnnotations;
- setter.parameters.add(binding);
- }
- desc.wrappers.add(setter);
- }
- }
-
- private String[] getParamNames(Object obj, int paramCount) {
- String[] paramNames = new String[paramCount];
- try {
- Object params = reflectCall(obj, "getParameters");
- for (int i = 0; i < paramNames.length; i++) {
- paramNames[i] = (String) reflectCall(Array.get(params, i), "getName");
- }
- } catch (Exception e) {
- }
- return paramNames;
- }
-
- private Object reflectCall(Object obj, String methodName, Object... args) throws Exception {
- Method method = obj.getClass().getMethod(methodName);
- return method.invoke(obj, args);
- }
-
- private void detectStaticFactory(ClassDescriptor desc, List allMethods) {
- for (Method method : allMethods) {
- if (!Modifier.isStatic(method.getModifiers())) {
- continue;
- }
- JsonCreator jsonCreator = getJsonCreator(method.getAnnotations());
- if (jsonCreator == null) {
- continue;
- }
- desc.ctor.staticMethodName = method.getName();
- desc.ctor.staticFactory = method;
- desc.ctor.ctor = null;
- Annotation[][] annotations = method.getParameterAnnotations();
- String[] paramNames = getParamNames(method, annotations.length);
- for (int i = 0; i < annotations.length; i++) {
- Annotation[] paramAnnotations = annotations[i];
- JsonProperty jsonProperty = getJsonProperty(paramAnnotations);
- Binding binding = new Binding(desc.clazz, desc.lookup, method.getGenericParameterTypes()[i]);
- if (jsonProperty != null) {
- binding.name = jsonProperty.value();
- if (jsonProperty.required()) {
- binding.asMissingWhenNotPresent = true;
- }
- }
- if (binding.name == null || binding.name.length() == 0) {
- binding.name = paramNames[i];
- }
- binding.annotations = paramAnnotations;
- desc.ctor.parameters.add(binding);
- }
- }
- }
-
- private void detectCtor(ClassDescriptor desc) {
- for (Constructor ctor : desc.clazz.getDeclaredConstructors()) {
- JsonCreator jsonCreator = getJsonCreator(ctor.getAnnotations());
- if (jsonCreator == null) {
- continue;
- }
- desc.ctor.staticMethodName = null;
- desc.ctor.ctor = ctor;
- desc.ctor.staticFactory = null;
- Annotation[][] annotations = ctor.getParameterAnnotations();
- String[] paramNames = getParamNames(ctor, annotations.length);
- for (int i = 0; i < annotations.length; i++) {
- Annotation[] paramAnnotations = annotations[i];
- JsonProperty jsonProperty = getJsonProperty(paramAnnotations);
- Binding binding = new Binding(desc.clazz, desc.lookup, ctor.getGenericParameterTypes()[i]);
- if (jsonProperty != null) {
- binding.name = jsonProperty.value();
- if (jsonProperty.required()) {
- binding.asMissingWhenNotPresent = true;
- }
- }
- if (binding.name == null || binding.name.length() == 0) {
- binding.name = paramNames[i];
- }
- binding.annotations = paramAnnotations;
- desc.ctor.parameters.add(binding);
- }
- }
- }
-
- private void updateBindings(ClassDescriptor desc) {
- for (Binding binding : desc.allBindings()) {
- JsonIgnore jsonIgnore = getJsonIgnore(binding.annotations);
- if (jsonIgnore != null && jsonIgnore.value()) {
- binding.fromNames = new String[0];
- binding.toNames = new String[0];
- }
- JsonProperty jsonProperty = getJsonProperty(binding.annotations);
- if (jsonProperty != null) {
- String altName = jsonProperty.value();
- if (!altName.isEmpty()) {
- binding.name = altName;
- binding.fromNames = new String[]{altName};
- }
- if (jsonProperty.from().length > 0) {
- binding.fromNames = jsonProperty.from();
- }
- if (jsonProperty.to().length > 0) {
- binding.toNames = jsonProperty.to();
- }
- if (jsonProperty.required()) {
- binding.asMissingWhenNotPresent = true;
- }
- if (jsonProperty.decoder() != Decoder.class) {
- try {
- binding.decoder = jsonProperty.decoder().newInstance();
- } catch (Exception e) {
- throw new JsonException(e);
- }
- }
- if (jsonProperty.encoder() != Encoder.class) {
- try {
- binding.encoder = jsonProperty.encoder().newInstance();
- } catch (Exception e) {
- throw new JsonException(e);
- }
- }
- if (jsonProperty.implementation() != Object.class) {
- binding.valueType = ParameterizedTypeImpl.useImpl(binding.valueType, jsonProperty.implementation());
- binding.valueTypeLiteral = TypeLiteral.create(binding.valueType);
- }
- }
- if (getAnnotation(binding.annotations, JsonMissingProperties.class) != null) {
- // this binding will not bind from json
- // instead it will be set by jsoniter with missing property names
- binding.fromNames = new String[0];
- desc.onMissingProperties = binding;
- }
- if (getAnnotation(binding.annotations, JsonExtraProperties.class) != null) {
- // this binding will not bind from json
- // instead it will be set by jsoniter with extra properties
- binding.fromNames = new String[0];
- desc.onExtraProperties = binding;
- }
- }
- }
-
- protected JsonCreator getJsonCreator(Annotation[] annotations) {
- return getAnnotation(annotations, JsonCreator.class);
- }
-
- protected JsonProperty getJsonProperty(Annotation[] annotations) {
- return getAnnotation(annotations, JsonProperty.class);
- }
-
- protected JsonIgnore getJsonIgnore(Annotation[] annotations) {
- return getAnnotation(annotations, JsonIgnore.class);
- }
-
- protected static T getAnnotation(Annotation[] annotations, Class annotationClass) {
- if (annotations == null) {
- return null;
- }
- for (Annotation annotation : annotations) {
- if (annotationClass.isAssignableFrom(annotation.getClass())) {
- return (T) annotation;
- }
- }
- return null;
- }
-}
diff --git a/src/main/java/com/jsoniter/any/Any.java b/src/main/java/com/jsoniter/any/Any.java
index 1f82c890..8159f1c2 100644
--- a/src/main/java/com/jsoniter/any/Any.java
+++ b/src/main/java/com/jsoniter/any/Any.java
@@ -1,19 +1,25 @@
package com.jsoniter.any;
-import com.jsoniter.JsonException;
-import com.jsoniter.JsonIterator;
+import com.jsoniter.output.CodegenAccess;
+import com.jsoniter.spi.JsonException;
import com.jsoniter.ValueType;
import com.jsoniter.output.JsonStream;
import com.jsoniter.spi.Encoder;
import com.jsoniter.spi.TypeLiteral;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.*;
public abstract class Any implements Iterable {
static {
- Encoder anyEncoder = new Encoder() {
+ registerEncoders();
+ }
+
+ public static void registerEncoders() {
+ Encoder.ReflectionEncoder anyEncoder = new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
Any any = (Any) obj;
@@ -25,6 +31,7 @@ public Any wrap(Object obj) {
return (Any) obj;
}
};
+ JsonStream.registerNativeEncoder(Any.class, anyEncoder);
JsonStream.registerNativeEncoder(TrueAny.class, anyEncoder);
JsonStream.registerNativeEncoder(FalseAny.class, anyEncoder);
JsonStream.registerNativeEncoder(ArrayLazyAny.class, anyEncoder);
@@ -33,15 +40,47 @@ public Any wrap(Object obj) {
JsonStream.registerNativeEncoder(IntAny.class, anyEncoder);
JsonStream.registerNativeEncoder(LongAny.class, anyEncoder);
JsonStream.registerNativeEncoder(NullAny.class, anyEncoder);
+ JsonStream.registerNativeEncoder(LongLazyAny.class, anyEncoder);
JsonStream.registerNativeEncoder(DoubleLazyAny.class, anyEncoder);
JsonStream.registerNativeEncoder(ObjectLazyAny.class, anyEncoder);
JsonStream.registerNativeEncoder(StringAny.class, anyEncoder);
JsonStream.registerNativeEncoder(StringLazyAny.class, anyEncoder);
JsonStream.registerNativeEncoder(ArrayAny.class, anyEncoder);
JsonStream.registerNativeEncoder(ObjectAny.class, anyEncoder);
+ JsonStream.registerNativeEncoder(ListWrapperAny.class, anyEncoder);
+ JsonStream.registerNativeEncoder(ArrayWrapperAny.class, anyEncoder);
+ JsonStream.registerNativeEncoder(MapWrapperAny.class, anyEncoder);
+ }
+
+ public static Any wrapArray(Object val) {
+ return new ArrayWrapperAny(val);
+ }
+
+ public interface EntryIterator {
+ boolean next();
+
+ String key();
+
+ Any value();
}
protected final static Set EMPTY_KEYS = Collections.unmodifiableSet(new HashSet());
+ protected final static EntryIterator EMPTY_ENTRIES_ITERATOR = new EntryIterator() {
+ @Override
+ public boolean next() {
+ return false;
+ }
+
+ @Override
+ public String key() {
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public Any value() {
+ throw new NoSuchElementException();
+ }
+ };
protected final static Iterator EMPTY_ITERATOR = new Iterator() {
@Override
public void remove() {
@@ -55,18 +94,14 @@ public boolean hasNext() {
@Override
public Any next() {
- throw new UnsupportedOperationException();
+ throw new NoSuchElementException();
}
};
public abstract ValueType valueType();
public T bindTo(T obj, Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return null;
- }
- return found.bindTo(obj);
+ return get(keys).bindTo(obj);
}
public T bindTo(T obj) {
@@ -74,11 +109,7 @@ public T bindTo(T obj) {
}
public T bindTo(TypeLiteral typeLiteral, T obj, Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return null;
- }
- return found.bindTo(typeLiteral, obj);
+ return get(keys).bindTo(typeLiteral, obj);
}
public T bindTo(TypeLiteral typeLiteral, T obj) {
@@ -86,11 +117,7 @@ public T bindTo(TypeLiteral typeLiteral, T obj) {
}
public Object object(Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return null;
- }
- return found.object();
+ return get(keys).object();
}
public abstract Object object();
@@ -104,11 +131,7 @@ public List asList() {
}
public T as(Class clazz, Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return null;
- }
- return found.as(clazz);
+ return get(keys).as(clazz);
}
public T as(Class clazz) {
@@ -116,11 +139,7 @@ public T as(Class clazz) {
}
public T as(TypeLiteral typeLiteral, Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return null;
- }
- return found.as(typeLiteral);
+ return get(keys).as(typeLiteral);
}
public T as(TypeLiteral typeLiteral) {
@@ -128,121 +147,91 @@ public T as(TypeLiteral typeLiteral) {
}
public final boolean toBoolean(Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return false;
- }
- return found.toBoolean();
+ return get(keys).toBoolean();
}
- public boolean toBoolean() {
- throw reportUnexpectedType(ValueType.BOOLEAN);
- }
+ public abstract boolean toBoolean();
public final int toInt(Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return 0;
- }
- return found.toInt();
+ return get(keys).toInt();
}
- public int toInt() {
- throw reportUnexpectedType(ValueType.NUMBER);
- }
+ public abstract int toInt();
public final long toLong(Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return 0;
- }
- return found.toLong();
+ return get(keys).toLong();
}
- public long toLong() {
- throw reportUnexpectedType(ValueType.NUMBER);
- }
+ public abstract long toLong();
public final float toFloat(Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return 0;
- }
- return found.toFloat();
+ return get(keys).toFloat();
}
- public float toFloat() {
- throw reportUnexpectedType(ValueType.NUMBER);
- }
+ public abstract float toFloat();
public final double toDouble(Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return 0;
- }
- return found.toDouble();
+ return get(keys).toDouble();
}
- public double toDouble() {
- throw reportUnexpectedType(ValueType.NUMBER);
- }
+ public abstract double toDouble();
+
+ public final BigInteger toBigInteger(Object ...keys) { return get(keys).toBigInteger(); }
+
+ public abstract BigInteger toBigInteger();
+
+ public final BigDecimal toBigDecimal(Object ...keys) { return get(keys).toBigDecimal(); }
+
+ public abstract BigDecimal toBigDecimal();
public final String toString(Object... keys) {
- Any found = get(keys);
- if (found == null) {
- return null;
- }
- return found.toString();
+ return get(keys).toString();
}
+ public abstract String toString();
public int size() {
return 0;
}
- public Set keys() {
- return LazyAny.EMPTY_KEYS;
+ public Any mustBeValid() {
+ if(this instanceof NotFoundAny) {
+ throw ((NotFoundAny) this).exception;
+ } else {
+ return this;
+ }
+ }
+
+ public Set keys() {
+ return EMPTY_KEYS;
}
@Override
public Iterator iterator() {
- return LazyAny.EMPTY_ITERATOR;
+ return EMPTY_ITERATOR;
+ }
+
+ public EntryIterator entries() {
+ return EMPTY_ENTRIES_ITERATOR;
}
public Any get(int index) {
- return null;
+ return new NotFoundAny(index, object());
}
public Any get(Object key) {
- return null;
+ return new NotFoundAny(key, object());
}
public final Any get(Object... keys) {
- try {
- return get(keys, 0);
- } catch (IndexOutOfBoundsException e) {
- return null;
- } catch (ClassCastException e) {
- return null;
- }
+ return get(keys, 0);
}
public Any get(Object[] keys, int idx) {
if (idx == keys.length) {
return this;
}
- return null;
- }
-
- public final Any require(Object... keys) {
- return require(keys, 0);
- }
-
- public Any require(Object[] keys, int idx) {
- if (idx == keys.length) {
- return this;
- }
- throw reportPathNotFound(keys, idx);
+ return new NotFoundAny(keys, idx, object());
}
public Any set(int newVal) {
@@ -265,17 +254,8 @@ public Any set(String newVal) {
return wrap(newVal);
}
- public JsonIterator parse() {
- throw new UnsupportedOperationException();
- }
-
public abstract void writeTo(JsonStream stream) throws IOException;
- protected JsonException reportPathNotFound(Object[] keys, int idx) {
- throw new JsonException(String.format("failed to get path %s, because #%s %s not found in %s",
- Arrays.toString(keys), idx, keys[idx], object()));
- }
-
protected JsonException reportUnexpectedType(ValueType toType) {
throw new JsonException(String.format("can not convert %s to %s", valueType(), toType));
}
@@ -335,37 +315,61 @@ public static Any wrap(Collection val) {
if (val == null) {
return NullAny.INSTANCE;
}
- ArrayList copied = new ArrayList(val.size());
- for (T element : val) {
- copied.add(wrap(element));
+ return new ListWrapperAny(new ArrayList(val));
+ }
+
+ public static Any wrap(List val) {
+ if (val == null) {
+ return NullAny.INSTANCE;
}
- return new ArrayAny(copied);
+ return new ListWrapperAny(val);
}
public static Any wrap(Map val) {
if (val == null) {
return NullAny.INSTANCE;
}
- HashMap copied = new HashMap(val.size());
- for (Map.Entry entry : val.entrySet()) {
- copied.put(entry.getKey(), wrap(entry.getValue()));
- }
- return new ObjectAny(copied);
+ return new MapWrapperAny(val);
}
public static Any wrap(Object val) {
- return JsonStream.wrap(val);
+ return CodegenAccess.wrap(val);
}
public static Any wrapNull() {
return NullAny.INSTANCE;
}
- public static Any wrapAnyList(List val) {
+ public static Any rewrap(List val) {
return new ArrayAny(val);
}
- public static Any wrapAnyMap(Map val) {
+ public static Any rewrap(Map val) {
return new ObjectAny(val);
}
+
+ private final static int wildcardHashCode = Character.valueOf('*').hashCode();
+ private final static Character wildcard = '*';
+
+ protected boolean isWildcard(Object key) {
+ return wildcardHashCode == key.hashCode() && wildcard.equals(key);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Any any = (Any) o;
+
+ Object obj = this.object();
+ Object thatObj = any.object();
+ return obj != null ? obj.equals(thatObj) : thatObj == null;
+ }
+
+ @Override
+ public int hashCode() {
+ Object obj = this.object();
+ return obj != null ? obj.hashCode() : 0;
+ }
}
diff --git a/src/main/java/com/jsoniter/any/ArrayAny.java b/src/main/java/com/jsoniter/any/ArrayAny.java
index 44e80a46..50d5c174 100644
--- a/src/main/java/com/jsoniter/any/ArrayAny.java
+++ b/src/main/java/com/jsoniter/any/ArrayAny.java
@@ -4,10 +4,13 @@
import com.jsoniter.output.JsonStream;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
-public class ArrayAny extends Any {
+class ArrayAny extends Any {
private final List val;
@@ -34,7 +37,7 @@ public void writeTo(JsonStream stream) throws IOException {
return;
}
iter.next().writeTo(stream);
- while(iter.hasNext()) {
+ while (iter.hasNext()) {
stream.writeMore();
iter.next().writeTo(stream);
}
@@ -53,7 +56,11 @@ public Iterator iterator() {
@Override
public Any get(int index) {
- return val.get(index);
+ try {
+ return val.get(index);
+ } catch (IndexOutOfBoundsException e) {
+ return new NotFoundAny(index, object());
+ }
}
@Override
@@ -61,21 +68,24 @@ public Any get(Object[] keys, int idx) {
if (idx == keys.length) {
return this;
}
- return val.get((Integer) keys[idx]).get(keys, idx+1);
- }
-
- @Override
- public Any require(Object[] keys, int idx) {
- if (idx == keys.length) {
- return this;
+ Object key = keys[idx];
+ if (isWildcard(key)) {
+ ArrayList result = new ArrayList();
+ for (Any element : val) {
+ Any mapped = element.get(keys, idx + 1);
+ if (mapped.valueType() != ValueType.INVALID) {
+ result.add(mapped);
+ }
+ }
+ return Any.rewrap(result);
}
- Any result = null;
try {
- result = val.get((Integer) keys[idx]);
+ return val.get((Integer) key).get(keys, idx + 1);
} catch (IndexOutOfBoundsException e) {
- reportPathNotFound(keys, idx);
+ return new NotFoundAny(keys, idx, object());
+ } catch (ClassCastException e) {
+ return new NotFoundAny(keys, idx, object());
}
- return result.require(keys, idx + 1);
}
@Override
@@ -87,4 +97,34 @@ public String toString() {
public boolean toBoolean() {
return !val.isEmpty();
}
+
+ @Override
+ public int toInt() {
+ return val.size();
+ }
+
+ @Override
+ public long toLong() {
+ return val.size();
+ }
+
+ @Override
+ public float toFloat() {
+ return val.size();
+ }
+
+ @Override
+ public double toDouble() {
+ return val.size();
+ }
+
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf(val.size());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(val.size());
+ }
}
diff --git a/src/main/java/com/jsoniter/any/ArrayLazyAny.java b/src/main/java/com/jsoniter/any/ArrayLazyAny.java
index 849a9f79..13983641 100644
--- a/src/main/java/com/jsoniter/any/ArrayLazyAny.java
+++ b/src/main/java/com/jsoniter/any/ArrayLazyAny.java
@@ -1,18 +1,27 @@
package com.jsoniter.any;
import com.jsoniter.*;
+import com.jsoniter.output.JsonStream;
+import com.jsoniter.spi.JsonException;
+import com.jsoniter.spi.TypeLiteral;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
class ArrayLazyAny extends LazyAny {
+ private final static TypeLiteral> typeLiteral = new TypeLiteral>() {
+ };
private List cache;
+ private int lastParsedPos;
public ArrayLazyAny(byte[] data, int head, int tail) {
super(data, head, tail);
+ lastParsedPos = head;
}
@Override
@@ -28,13 +37,46 @@ public Object object() {
@Override
public boolean toBoolean() {
+ JsonIterator iter = parse();
try {
- return CodegenAccess.readArrayStart(parse());
+ return CodegenAccess.readArrayStart(iter);
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
+ @Override
+ public int toInt() {
+ return size();
+ }
+
+ @Override
+ public long toLong() {
+ return size();
+ }
+
+ @Override
+ public float toFloat() {
+ return size();
+ }
+
+ @Override
+ public double toDouble() {
+ return size();
+ }
+
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf(size());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(size());
+ }
+
@Override
public int size() {
fillCache();
@@ -43,19 +85,19 @@ public int size() {
@Override
public Iterator iterator() {
- fillCache();
- return new ArrayIterator(cache);
+ if (lastParsedPos == tail) {
+ return cache.iterator();
+ } else {
+ return new LazyIterator();
+ }
}
@Override
public Any get(int index) {
try {
- fillCache();
- return cache.get(index);
+ return fillCacheUntil(index);
} catch (IndexOutOfBoundsException e) {
- return null;
- } catch (ClassCastException e) {
- return null;
+ return new NotFoundAny(index, object());
}
}
@@ -64,54 +106,111 @@ public Any get(Object[] keys, int idx) {
if (idx == keys.length) {
return this;
}
- fillCache();
- return cache.get((Integer) keys[idx]).get(keys, idx+1);
- }
-
- @Override
- public Any require(Object[] keys, int idx) {
- if (idx == keys.length) {
- return this;
+ Object key = keys[idx];
+ if (isWildcard(key)) {
+ fillCache();
+ ArrayList result = new ArrayList();
+ for (Any element : cache) {
+ Any mapped = element.get(keys, idx + 1);
+ if (mapped.valueType() != ValueType.INVALID) {
+ result.add(mapped);
+ }
+ }
+ return Any.rewrap(result);
}
- Any result = null;
try {
- fillCache();
- result = cache.get((Integer) keys[idx]);
+ return fillCacheUntil((Integer) key).get(keys, idx + 1);
} catch (IndexOutOfBoundsException e) {
- reportPathNotFound(keys, idx);
+ return new NotFoundAny(keys, idx, object());
+ } catch (ClassCastException e) {
+ return new NotFoundAny(keys, idx, object());
}
- return result.require(keys, idx + 1);
}
private void fillCache() {
- if (cache != null) {
+ if (lastParsedPos == tail) {
return;
}
- try {
- JsonIterator iter = parse();
+ if (cache == null) {
cache = new ArrayList(4);
- if (!CodegenAccess.readArrayStart(iter)) {
- return;
+ }
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
+ try {
+ iter.reset(data, lastParsedPos, tail);
+ if (lastParsedPos == head) {
+ if (!CodegenAccess.readArrayStart(iter)) {
+ lastParsedPos = tail;
+ return;
+ }
+ cache.add(iter.readAny());
}
- cache.add(iter.readAny());
while (CodegenAccess.nextToken(iter) == ',') {
cache.add(iter.readAny());
}
+ lastParsedPos = tail;
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
- private static class ArrayIterator implements Iterator {
+ private Any fillCacheUntil(int target) {
+ if (lastParsedPos == tail) {
+ return cache.get(target);
+ }
+ if (cache == null) {
+ cache = new ArrayList(4);
+ }
+ int i = cache.size();
+ if (target < i) {
+ return cache.get(target);
+ }
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
+ try {
+ iter.reset(data, lastParsedPos, tail);
+ if (lastParsedPos == head) {
+ if (!CodegenAccess.readArrayStart(iter)) {
+ lastParsedPos = tail;
+ throw new IndexOutOfBoundsException();
+ }
+ Any element = iter.readAny();
+ cache.add(element);
+ if (target == 0) {
+ lastParsedPos = CodegenAccess.head(iter);
+ return element;
+ }
+ i = 1;
+ }
+ while (CodegenAccess.nextToken(iter) == ',') {
+ Any element = iter.readAny();
+ cache.add(element);
+ if (i++ == target) {
+ lastParsedPos = CodegenAccess.head(iter);
+ return element;
+ }
+ }
+ lastParsedPos = tail;
+ } catch (IOException e) {
+ throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
+ }
+ throw new IndexOutOfBoundsException();
+ }
+
+ private class LazyIterator implements Iterator {
- private final int size;
- private final List array;
- private int idx;
+ private Any next;
+ private int index;
- public ArrayIterator(List array) {
- size = array.size();
- this.array = array;
- idx = 0;
+ public LazyIterator() {
+ index = 0;
+ try {
+ next = fillCacheUntil(index);
+ } catch (IndexOutOfBoundsException e) {
+ next = null;
+ }
}
@Override
@@ -121,12 +220,43 @@ public void remove() {
@Override
public boolean hasNext() {
- return idx < size;
+ return next != null;
}
@Override
public Any next() {
- return array.get(idx++);
+ if (next == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ Any current = next;
+ try {
+ index++;
+ next = fillCacheUntil(index);
+ } catch (IndexOutOfBoundsException e){
+ next = null;
+ }
+ return current;
+ }
+ }
+
+ @Override
+ public void writeTo(JsonStream stream) throws IOException {
+ if (lastParsedPos == head) {
+ super.writeTo(stream);
+ } else {
+ // there might be modification
+ fillCache();
+ stream.writeVal(typeLiteral, cache);
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (lastParsedPos == head) {
+ return super.toString();
+ } else {
+ fillCache();
+ return JsonStream.serialize(cache);
}
}
}
diff --git a/src/main/java/com/jsoniter/any/ArrayWrapperAny.java b/src/main/java/com/jsoniter/any/ArrayWrapperAny.java
new file mode 100644
index 00000000..f5693663
--- /dev/null
+++ b/src/main/java/com/jsoniter/any/ArrayWrapperAny.java
@@ -0,0 +1,191 @@
+package com.jsoniter.any;
+
+import com.jsoniter.ValueType;
+import com.jsoniter.output.JsonStream;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+class ArrayWrapperAny extends Any {
+
+ private final Object val;
+ private List cache;
+
+ public ArrayWrapperAny(Object val) {
+ this.val = val;
+ }
+
+ @Override
+ public ValueType valueType() {
+ return ValueType.ARRAY;
+ }
+
+ @Override
+ public Object object() {
+ fillCache();
+ return cache;
+ }
+
+ @Override
+ public boolean toBoolean() {
+ return size() != 0;
+ }
+
+ @Override
+ public int toInt() {
+ return size();
+ }
+
+ @Override
+ public long toLong() {
+ return size();
+ }
+
+ @Override
+ public float toFloat() {
+ return size();
+ }
+
+ @Override
+ public double toDouble() {
+ return size();
+ }
+
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf(size());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(size());
+ }
+
+ @Override
+ public String toString() {
+ if (cache == null) {
+ return JsonStream.serialize(val);
+ } else {
+ fillCache();
+ return JsonStream.serialize(cache);
+ }
+ }
+
+ @Override
+ public void writeTo(JsonStream stream) throws IOException {
+ if (cache == null) {
+ stream.writeVal(val);
+ } else {
+ fillCache();
+ stream.writeVal(cache);
+ }
+ }
+
+ @Override
+ public int size() {
+ return Array.getLength(val);
+ }
+
+ @Override
+ public Any get(int index) {
+ return fillCacheUntil(index);
+ }
+
+ @Override
+ public Any get(Object[] keys, int idx) {
+ if (idx == keys.length) {
+ return this;
+ }
+ Object key = keys[idx];
+ if (isWildcard(key)) {
+ fillCache();
+ ArrayList result = new ArrayList();
+ for (Any element : cache) {
+ Any mapped = element.get(keys, idx + 1);
+ if (mapped.valueType() != ValueType.INVALID) {
+ result.add(mapped);
+ }
+ }
+ return Any.rewrap(result);
+ }
+ try {
+ return fillCacheUntil((Integer) key).get(keys, idx + 1);
+ } catch (IndexOutOfBoundsException e) {
+ return new NotFoundAny(keys, idx, object());
+ } catch (ClassCastException e) {
+ return new NotFoundAny(keys, idx, object());
+ }
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new WrapperIterator();
+ }
+
+ private Any fillCacheUntil(int index) {
+ if (cache == null) {
+ cache = new ArrayList();
+ }
+ if (index < cache.size()) {
+ return cache.get(index);
+ }
+ for (int i = cache.size(); i < size(); i++) {
+ Any element = Any.wrap(Array.get(val, i));
+ cache.add(element);
+ if (index == i) {
+ return element;
+ }
+ }
+ return new NotFoundAny(index, val);
+ }
+
+ private void fillCache() {
+ if (cache == null) {
+ cache = new ArrayList();
+ }
+ int size = size();
+ if (cache.size() == size) {
+ return;
+ }
+ for (int i = cache.size(); i < size; i++) {
+ Any element = Any.wrap(Array.get(val, i));
+ cache.add(element);
+ }
+ }
+
+ private class WrapperIterator implements Iterator {
+
+ private int index;
+ private final int size;
+
+ private WrapperIterator() {
+ size = size();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return index < size;
+ }
+
+ @Override
+ public Any next() {
+ if (cache == null) {
+ cache = new ArrayList();
+ }
+ if (index == cache.size()) {
+ cache.add(Any.wrap(Array.get(val, index)));
+ }
+ return cache.get(index++);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/src/main/java/com/jsoniter/any/DoubleAny.java b/src/main/java/com/jsoniter/any/DoubleAny.java
index b9937843..64bcb88d 100644
--- a/src/main/java/com/jsoniter/any/DoubleAny.java
+++ b/src/main/java/com/jsoniter/any/DoubleAny.java
@@ -2,9 +2,10 @@
import com.jsoniter.ValueType;
import com.jsoniter.output.JsonStream;
-import com.jsoniter.spi.TypeLiteral;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class DoubleAny extends Any {
@@ -49,6 +50,16 @@ public double toDouble() {
return val;
}
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf((long) val);
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(val);
+ }
+
@Override
public String toString() {
return String.valueOf(val);
diff --git a/src/main/java/com/jsoniter/any/DoubleLazyAny.java b/src/main/java/com/jsoniter/any/DoubleLazyAny.java
index 5fce4cf9..edc32774 100644
--- a/src/main/java/com/jsoniter/any/DoubleLazyAny.java
+++ b/src/main/java/com/jsoniter/any/DoubleLazyAny.java
@@ -1,9 +1,13 @@
package com.jsoniter.any;
-import com.jsoniter.JsonException;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.JsonIteratorPool;
+import com.jsoniter.spi.JsonException;
import com.jsoniter.ValueType;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class DoubleLazyAny extends LazyAny {
@@ -55,12 +59,25 @@ public double toDouble() {
return cache;
}
+ @Override
+ public BigInteger toBigInteger() {
+ return new BigInteger(toString());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return new BigDecimal(toString());
+ }
+
private void fillCache() {
if (!isCached) {
+ JsonIterator iter = parse();
try {
- cache = parse().readDouble();
+ cache = iter.readDouble();
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
isCached = true;
}
diff --git a/src/main/java/com/jsoniter/any/FalseAny.java b/src/main/java/com/jsoniter/any/FalseAny.java
index a516e96e..6d7a7288 100644
--- a/src/main/java/com/jsoniter/any/FalseAny.java
+++ b/src/main/java/com/jsoniter/any/FalseAny.java
@@ -4,6 +4,8 @@
import com.jsoniter.output.JsonStream;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class FalseAny extends Any {
@@ -44,6 +46,16 @@ public double toDouble() {
return 0;
}
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.ZERO;
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.ZERO;
+ }
+
@Override
public String toString() {
return "false";
diff --git a/src/main/java/com/jsoniter/any/FloatAny.java b/src/main/java/com/jsoniter/any/FloatAny.java
index b6d29f03..7063f321 100644
--- a/src/main/java/com/jsoniter/any/FloatAny.java
+++ b/src/main/java/com/jsoniter/any/FloatAny.java
@@ -5,6 +5,8 @@
import com.jsoniter.spi.TypeLiteral;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class FloatAny extends Any {
@@ -49,6 +51,16 @@ public double toDouble() {
return val;
}
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf((long) val);
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(val);
+ }
+
@Override
public String toString() {
return String.valueOf(val);
diff --git a/src/main/java/com/jsoniter/any/IntAny.java b/src/main/java/com/jsoniter/any/IntAny.java
index ff408443..fbb5f074 100644
--- a/src/main/java/com/jsoniter/any/IntAny.java
+++ b/src/main/java/com/jsoniter/any/IntAny.java
@@ -4,6 +4,8 @@
import com.jsoniter.output.JsonStream;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class IntAny extends Any {
@@ -48,6 +50,16 @@ public double toDouble() {
return val;
}
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf(val);
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(val);
+ }
+
@Override
public String toString() {
return String.valueOf(val);
diff --git a/src/main/java/com/jsoniter/any/LazyAny.java b/src/main/java/com/jsoniter/any/LazyAny.java
index 1402eb26..d088241e 100644
--- a/src/main/java/com/jsoniter/any/LazyAny.java
+++ b/src/main/java/com/jsoniter/any/LazyAny.java
@@ -1,6 +1,7 @@
package com.jsoniter.any;
-import com.jsoniter.JsonException;
+import com.jsoniter.JsonIteratorPool;
+import com.jsoniter.spi.JsonException;
import com.jsoniter.JsonIterator;
import com.jsoniter.ValueType;
import com.jsoniter.output.JsonStream;
@@ -10,12 +11,6 @@
abstract class LazyAny extends Any {
- protected final static ThreadLocal tlsIter = new ThreadLocal() {
- @Override
- protected JsonIterator initialValue() {
- return new JsonIterator();
- }
- };
protected final byte[] data;
protected final int head;
protected final int tail;
@@ -29,43 +24,55 @@ public LazyAny(byte[] data, int head, int tail) {
public abstract ValueType valueType();
public final T bindTo(T obj) {
+ JsonIterator iter = parse();
try {
- return parse().read(obj);
+ return iter.read(obj);
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
public final T bindTo(TypeLiteral typeLiteral, T obj) {
+ JsonIterator iter = parse();
try {
- return parse().read(typeLiteral, obj);
+ return iter.read(typeLiteral, obj);
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
public final T as(Class clazz) {
+ JsonIterator iter = parse();
try {
- return parse().read(clazz);
+ return iter.read(clazz);
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
public final T as(TypeLiteral typeLiteral) {
+ JsonIterator iter = parse();
try {
- return parse().read(typeLiteral);
+ return iter.read(typeLiteral);
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
public String toString() {
- return new String(data, head, tail - head);
+ return new String(data, head, tail - head).trim();
}
- public final JsonIterator parse() {
- JsonIterator iter = tlsIter.get();
+ protected final JsonIterator parse() {
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
iter.reset(data, head, tail);
return iter;
}
diff --git a/src/main/java/com/jsoniter/any/ListWrapperAny.java b/src/main/java/com/jsoniter/any/ListWrapperAny.java
new file mode 100644
index 00000000..44345148
--- /dev/null
+++ b/src/main/java/com/jsoniter/any/ListWrapperAny.java
@@ -0,0 +1,184 @@
+package com.jsoniter.any;
+
+import com.jsoniter.ValueType;
+import com.jsoniter.output.JsonStream;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+class ListWrapperAny extends Any {
+
+ private final List val;
+ private List cache;
+
+ public ListWrapperAny(List val) {
+ this.val = val;
+ }
+
+ @Override
+ public ValueType valueType() {
+ return ValueType.ARRAY;
+ }
+
+ @Override
+ public Object object() {
+ fillCache();
+ return cache;
+ }
+
+ @Override
+ public boolean toBoolean() {
+ return !val.isEmpty();
+ }
+
+ @Override
+ public int toInt() {
+ return size();
+ }
+
+ @Override
+ public long toLong() {
+ return size();
+ }
+
+ @Override
+ public float toFloat() {
+ return size();
+ }
+
+ @Override
+ public double toDouble() {
+ return size();
+ }
+
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf(size());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(size());
+ }
+
+ @Override
+ public String toString() {
+ if (cache == null) {
+ return JsonStream.serialize(val);
+ } else {
+ fillCache();
+ return JsonStream.serialize(cache);
+ }
+ }
+
+ @Override
+ public void writeTo(JsonStream stream) throws IOException {
+ if (cache == null) {
+ stream.writeVal(val);
+ } else {
+ fillCache();
+ stream.writeVal(cache);
+ }
+ }
+
+ @Override
+ public int size() {
+ return val.size();
+ }
+
+ @Override
+ public Any get(int index) {
+ return fillCacheUntil(index);
+ }
+
+ @Override
+ public Any get(Object[] keys, int idx) {
+ if (idx == keys.length) {
+ return this;
+ }
+ Object key = keys[idx];
+ if (isWildcard(key)) {
+ fillCache();
+ ArrayList result = new ArrayList();
+ for (Any element : cache) {
+ Any mapped = element.get(keys, idx + 1);
+ if (mapped.valueType() != ValueType.INVALID) {
+ result.add(mapped);
+ }
+ }
+ return Any.rewrap(result);
+ }
+ try {
+ return fillCacheUntil((Integer) key).get(keys, idx + 1);
+ } catch (IndexOutOfBoundsException e) {
+ return new NotFoundAny(keys, idx, object());
+ } catch (ClassCastException e) {
+ return new NotFoundAny(keys, idx, object());
+ }
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new WrapperIterator();
+ }
+
+ private void fillCache() {
+ if (cache == null) {
+ cache = new ArrayList();
+ }
+ if (cache.size() == val.size()) {
+ return;
+ }
+ for (int i = cache.size(); i < val.size(); i++) {
+ Any element = Any.wrap(val.get(i));
+ cache.add(element);
+ }
+ }
+
+ private Any fillCacheUntil(int index) {
+ if (cache == null) {
+ cache = new ArrayList();
+ }
+ if (index < cache.size()) {
+ return cache.get(index);
+ }
+ for (int i = cache.size(); i < val.size(); i++) {
+ Any element = Any.wrap(val.get(i));
+ cache.add(element);
+ if (index == i) {
+ return element;
+ }
+ }
+ return new NotFoundAny(index, val);
+ }
+
+ private class WrapperIterator implements Iterator {
+
+ private int index;
+
+ @Override
+ public boolean hasNext() {
+ return index < val.size();
+ }
+
+ @Override
+ public Any next() {
+ if (cache == null) {
+ cache = new ArrayList();
+ }
+ if (index == cache.size()) {
+ cache.add(Any.wrap(val.get(index)));
+ }
+ return cache.get(index++);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/src/main/java/com/jsoniter/any/LongAny.java b/src/main/java/com/jsoniter/any/LongAny.java
index 283b2f7c..76f683eb 100644
--- a/src/main/java/com/jsoniter/any/LongAny.java
+++ b/src/main/java/com/jsoniter/any/LongAny.java
@@ -2,9 +2,10 @@
import com.jsoniter.ValueType;
import com.jsoniter.output.JsonStream;
-import com.jsoniter.spi.TypeLiteral;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class LongAny extends Any {
@@ -49,6 +50,16 @@ public double toDouble() {
return val;
}
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf(val);
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(val);
+ }
+
@Override
public String toString() {
return String.valueOf(val);
diff --git a/src/main/java/com/jsoniter/any/LongLazyAny.java b/src/main/java/com/jsoniter/any/LongLazyAny.java
index 305e0424..3a60dc74 100644
--- a/src/main/java/com/jsoniter/any/LongLazyAny.java
+++ b/src/main/java/com/jsoniter/any/LongLazyAny.java
@@ -1,9 +1,13 @@
package com.jsoniter.any;
-import com.jsoniter.JsonException;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.JsonIteratorPool;
+import com.jsoniter.spi.JsonException;
import com.jsoniter.ValueType;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class LongLazyAny extends LazyAny {
@@ -55,12 +59,25 @@ public double toDouble() {
return cache;
}
+ @Override
+ public BigInteger toBigInteger() {
+ return new BigInteger(toString());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return new BigDecimal(toString());
+ }
+
private void fillCache() {
if (!isCached) {
+ JsonIterator iter = parse();
try {
- cache = parse().readLong();
+ cache = iter.readLong();
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
isCached = true;
}
diff --git a/src/main/java/com/jsoniter/any/MapWrapperAny.java b/src/main/java/com/jsoniter/any/MapWrapperAny.java
new file mode 100644
index 00000000..5897ba1e
--- /dev/null
+++ b/src/main/java/com/jsoniter/any/MapWrapperAny.java
@@ -0,0 +1,206 @@
+package com.jsoniter.any;
+
+import com.jsoniter.ValueType;
+import com.jsoniter.output.JsonStream;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+class MapWrapperAny extends Any {
+
+ private final Map val;
+ private Map cache;
+
+ public MapWrapperAny(Map val) {
+ this.val = val;
+ }
+
+ @Override
+ public ValueType valueType() {
+ return ValueType.OBJECT;
+ }
+
+ @Override
+ public Object object() {
+ fillCache();
+ return cache;
+ }
+
+ @Override
+ public boolean toBoolean() {
+ return size() != 0;
+ }
+
+ @Override
+ public int toInt() {
+ return size();
+ }
+
+ @Override
+ public long toLong() {
+ return size();
+ }
+
+ @Override
+ public float toFloat() {
+ return size();
+ }
+
+ @Override
+ public double toDouble() {
+ return size();
+ }
+
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf(size());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(size());
+ }
+
+ @Override
+ public String toString() {
+ if (cache == null) {
+ return JsonStream.serialize(val);
+ } else {
+ fillCache();
+ return JsonStream.serialize(cache);
+ }
+ }
+
+ @Override
+ public void writeTo(JsonStream stream) throws IOException {
+ if (cache == null) {
+ stream.writeVal(val);
+ } else {
+ fillCache();
+ stream.writeVal(cache);
+ }
+ }
+
+ @Override
+ public int size() {
+ return val.size();
+ }
+
+ @Override
+ public Any get(Object key) {
+ return fillCacheUntil(key);
+ }
+
+ @Override
+ public Any get(Object[] keys, int idx) {
+ if (idx == keys.length) {
+ return this;
+ }
+ Object key = keys[idx];
+ if (isWildcard(key)) {
+ fillCache();
+ HashMap result = new HashMap();
+ for (Map.Entry entry : cache.entrySet()) {
+ Any mapped = entry.getValue().get(keys, idx + 1);
+ if (mapped.valueType() != ValueType.INVALID) {
+ result.put(entry.getKey(), mapped);
+ }
+ }
+ return Any.rewrap(result);
+ }
+ Any child = fillCacheUntil(key);
+ if (child == null) {
+ return new NotFoundAny(keys, idx, object());
+ }
+ return child.get(keys, idx + 1);
+ }
+
+ @Override
+ public EntryIterator entries() {
+ return new WrapperIterator();
+ }
+
+ private Any fillCacheUntil(Object target) {
+ if (cache == null) {
+ cache = new HashMap();
+ }
+ Any element = cache.get(target);
+ if (element != null) {
+ return element;
+ }
+ Set> entries = val.entrySet();
+ int targetHashcode = target.hashCode();
+ for (Map.Entry entry : entries) {
+ String key = entry.getKey();
+ if (cache.containsKey(key)) {
+ continue;
+ }
+ element = Any.wrap(entry.getValue());
+ cache.put(key, element);
+ if (targetHashcode == key.hashCode() && target.equals(key)) {
+ return element;
+ }
+ }
+ return new NotFoundAny(target, val);
+ }
+
+ private void fillCache() {
+ if (cache == null) {
+ cache = new HashMap();
+ }
+ Set> entries = val.entrySet();
+ for (Map.Entry entry : entries) {
+ String key = entry.getKey();
+ if (cache.containsKey(key)) {
+ continue;
+ }
+ Any element = Any.wrap(entry.getValue());
+ cache.put(key, element);
+ }
+ }
+
+ private class WrapperIterator implements EntryIterator {
+
+ private final Iterator> iter;
+ private String key;
+ private Any value;
+
+ private WrapperIterator() {
+ Set> entries = val.entrySet();
+ iter = entries.iterator();
+ }
+
+ @Override
+ public boolean next() {
+ if (cache == null) {
+ cache = new HashMap();
+ }
+ if (!iter.hasNext()) {
+ return false;
+ }
+ Map.Entry entry = iter.next();
+ key = entry.getKey();
+ value = cache.get(key);
+ if (value == null) {
+ value = Any.wrap(entry.getValue());
+ cache.put(key, value);
+ }
+ return true;
+ }
+
+ @Override
+ public String key() {
+ return key;
+ }
+
+ @Override
+ public Any value() {
+ return value;
+ }
+ }
+}
diff --git a/src/main/java/com/jsoniter/any/NotFoundAny.java b/src/main/java/com/jsoniter/any/NotFoundAny.java
new file mode 100644
index 00000000..1a5439ea
--- /dev/null
+++ b/src/main/java/com/jsoniter/any/NotFoundAny.java
@@ -0,0 +1,100 @@
+package com.jsoniter.any;
+
+import com.jsoniter.ValueType;
+import com.jsoniter.output.JsonStream;
+import com.jsoniter.spi.JsonException;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+
+class NotFoundAny extends Any {
+
+ protected final JsonException exception;
+
+ public NotFoundAny(Object[] keys, int idx, Object obj) {
+ this.exception = new JsonException(String.format("Value not found: failed to get path %s, because #%s section of the path ( %s ) not found in %s",
+ Arrays.toString(keys), idx, keys[idx], obj));
+ }
+
+ public NotFoundAny(int index, Object obj) {
+ this.exception = new JsonException(String.format("Value not found: failed to get index %s from %s",
+ index, obj));
+ }
+
+ public NotFoundAny(Object key, Object obj) {
+ this.exception = new JsonException(String.format("Value not found: failed to get key %s from %s",
+ key, obj));
+ }
+
+ @Override
+ public ValueType valueType() {
+ return ValueType.INVALID;
+ }
+
+ @Override
+ public Object object() {
+ throw exception;
+ }
+
+ @Override
+ public void writeTo(JsonStream stream) throws IOException {
+ throw exception;
+ }
+
+ @Override
+ public Any get(int index) {
+ return this;
+ }
+
+ @Override
+ public Any get(Object key) {
+ return this;
+ }
+
+ @Override
+ public Any get(Object[] keys, int idx) {
+ return this;
+ }
+
+ @Override
+ public boolean toBoolean() {
+ return false;
+ }
+
+ @Override
+ public int toInt() {
+ return 0;
+ }
+
+ @Override
+ public long toLong() {
+ return 0;
+ }
+
+ @Override
+ public float toFloat() {
+ return 0;
+ }
+
+ @Override
+ public double toDouble() {
+ return 0;
+ }
+
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.ZERO;
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.ZERO;
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+}
diff --git a/src/main/java/com/jsoniter/any/NullAny.java b/src/main/java/com/jsoniter/any/NullAny.java
index d17e3bd2..e086a34b 100644
--- a/src/main/java/com/jsoniter/any/NullAny.java
+++ b/src/main/java/com/jsoniter/any/NullAny.java
@@ -4,6 +4,8 @@
import com.jsoniter.output.JsonStream;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class NullAny extends Any {
@@ -44,6 +46,16 @@ public double toDouble() {
return 0;
}
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.ZERO;
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.ZERO;
+ }
+
@Override
public void writeTo(JsonStream stream) throws IOException {
stream.writeNull();
diff --git a/src/main/java/com/jsoniter/any/ObjectAny.java b/src/main/java/com/jsoniter/any/ObjectAny.java
index bdd8c9fa..009a4f13 100644
--- a/src/main/java/com/jsoniter/any/ObjectAny.java
+++ b/src/main/java/com/jsoniter/any/ObjectAny.java
@@ -1,13 +1,16 @@
package com.jsoniter.any;
-import com.jsoniter.JsonException;
import com.jsoniter.ValueType;
import com.jsoniter.output.JsonStream;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
-public class ObjectAny extends Any {
+class ObjectAny extends Any {
private final Map val;
@@ -46,6 +49,36 @@ public boolean toBoolean() {
return !val.isEmpty();
}
+ @Override
+ public int toInt() {
+ return size();
+ }
+
+ @Override
+ public long toLong() {
+ return size();
+ }
+
+ @Override
+ public float toFloat() {
+ return size();
+ }
+
+ @Override
+ public double toDouble() {
+ return size();
+ }
+
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf(size());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(size());
+ }
+
@Override
public String toString() {
return JsonStream.serialize(this);
@@ -53,7 +86,11 @@ public String toString() {
@Override
public Any get(Object key) {
- return val.get(key);
+ Any element = val.get(key);
+ if (element == null) {
+ return new NotFoundAny(key, object());
+ }
+ return element;
}
@Override
@@ -61,22 +98,55 @@ public Any get(Object[] keys, int idx) {
if (idx == keys.length) {
return this;
}
- Any child = val.get(keys[idx]);
- if (child == null) {
- return null;
+ Object key = keys[idx];
+ if (isWildcard(key)) {
+ HashMap result = new HashMap();
+ for (Map.Entry entry : val.entrySet()) {
+ Any mapped = entry.getValue().get(keys, idx + 1);
+ if (mapped.valueType() != ValueType.INVALID) {
+ result.put(entry.getKey(), mapped);
+ }
+ }
+ return Any.rewrap(result);
+ }
+ Any element = val.get(key);
+ if (element == null) {
+ return new NotFoundAny(keys, idx, object());
}
- return child.get(keys, idx+1);
+ return element.get(keys, idx + 1);
}
@Override
- public Any require(Object[] keys, int idx) {
- if (idx == keys.length) {
- return this;
+ public EntryIterator entries() {
+ return new IteratorAdapter(val.entrySet().iterator());
+ }
+
+ public static class IteratorAdapter implements EntryIterator {
+
+ private final Iterator> iter;
+ private Map.Entry entry;
+
+ public IteratorAdapter(Iterator> iter) {
+ this.iter = iter;
+ }
+
+ @Override
+ public boolean next() {
+ if (iter.hasNext()) {
+ entry = iter.next();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String key() {
+ return entry.getKey();
}
- Any result = val.get(keys[idx]);
- if (result == null) {
- throw reportPathNotFound(keys, idx);
+
+ @Override
+ public Any value() {
+ return entry.getValue();
}
- return result.require(keys, idx + 1);
}
}
diff --git a/src/main/java/com/jsoniter/any/ObjectLazyAny.java b/src/main/java/com/jsoniter/any/ObjectLazyAny.java
index ba6874c7..1fb389b6 100644
--- a/src/main/java/com/jsoniter/any/ObjectLazyAny.java
+++ b/src/main/java/com/jsoniter/any/ObjectLazyAny.java
@@ -1,15 +1,22 @@
package com.jsoniter.any;
import com.jsoniter.*;
+import com.jsoniter.output.JsonStream;
+import com.jsoniter.spi.JsonException;
+import com.jsoniter.spi.TypeLiteral;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
import java.util.Set;
class ObjectLazyAny extends LazyAny {
- private Map cache;
+ private final static TypeLiteral> typeLiteral = new TypeLiteral>(){};
+ private Map cache;
private int lastParsedPos;
public ObjectLazyAny(byte[] data, int head, int tail) {
@@ -31,12 +38,47 @@ public Object object() {
@Override
public boolean toBoolean() {
try {
- return CodegenAccess.readObjectStart(parse());
+ JsonIterator iter = parse();
+ try {
+ return CodegenAccess.readObjectStart(iter);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
+ }
} catch (IOException e) {
throw new JsonException(e);
}
}
+ @Override
+ public int toInt() {
+ return size();
+ }
+
+ @Override
+ public long toLong() {
+ return size();
+ }
+
+ @Override
+ public float toFloat() {
+ return size();
+ }
+
+ @Override
+ public double toDouble() {
+ return size();
+ }
+
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.valueOf(size());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.valueOf(size());
+ }
+
@Override
public int size() {
fillCache();
@@ -51,15 +93,11 @@ public Set keys() {
@Override
public Any get(Object key) {
- try {
- return fillCache(key);
- } catch (IndexOutOfBoundsException e) {
- return null;
- } catch (ClassCastException e) {
- return null;
- } catch (IOException e) {
- throw new JsonException(e);
+ Any element = fillCacheUntil(key);
+ if (element == null) {
+ return new NotFoundAny(key, object());
}
+ return element;
}
@Override
@@ -67,97 +105,199 @@ public Any get(Object[] keys, int idx) {
if (idx == keys.length) {
return this;
}
- try {
- Any child = fillCache(keys[idx]);
- if (child == null) {
- return null;
+ Object key = keys[idx];
+ if (isWildcard(key)) {
+ fillCache();
+ HashMap result = new HashMap();
+ for (Map.Entry entry : cache.entrySet()) {
+ Any mapped = entry.getValue().get(keys, idx + 1);
+ if (mapped.valueType() != ValueType.INVALID) {
+ result.put(entry.getKey(), mapped);
+ }
}
- return child.get(keys, idx+1);
- } catch (IOException e) {
- throw new JsonException(e);
+ return Any.rewrap(result);
}
- }
-
- @Override
- public Any require(Object[] keys, int idx) {
- if (idx == keys.length) {
- return this;
- }
- try {
- Any result = fillCache(keys[idx]);
- if (result == null) {
- throw reportPathNotFound(keys, idx);
- }
- return result.require(keys, idx + 1);
- } catch (IOException e) {
- throw new JsonException(e);
+ Any child = fillCacheUntil(key);
+ if (child == null) {
+ return new NotFoundAny(keys, idx, object());
}
+ return child.get(keys, idx+1);
}
- private Any fillCache(Object target) throws IOException {
+ private Any fillCacheUntil(Object target) {
if (lastParsedPos == tail) {
return cache.get(target);
}
- if (cache != null) {
- Any value = cache.get(target);
- if (value != null) {
- return value;
- }
- }
- JsonIterator iter = tlsIter.get();
- iter.reset(data, lastParsedPos, tail);
if (cache == null) {
- cache = new HashMap(4);
+ cache = new HashMap(4);
}
- if (lastParsedPos == head) {
- if (!CodegenAccess.readObjectStart(iter)) {
- lastParsedPos = tail;
- return null;
- }
- String field = CodegenAccess.readObjectFieldAsString(iter);
- Any value = iter.readAny();
- cache.put(field, value);
- if (field.hashCode() == target.hashCode() && field.equals(target)) {
- lastParsedPos = CodegenAccess.head(iter);
- return value;
- }
+ Any value = cache.get(target);
+ if (value != null) {
+ return value;
}
- while (CodegenAccess.nextToken(iter) == ',') {
- String field = CodegenAccess.readObjectFieldAsString(iter);
- Any value = iter.readAny();
- cache.put(field, value);
- if (field.hashCode() == target.hashCode() && field.equals(target)) {
- lastParsedPos = CodegenAccess.head(iter);
- return value;
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
+ try {
+ iter.reset(data, lastParsedPos, tail);
+ if (lastParsedPos == head) {
+ if (!CodegenAccess.readObjectStart(iter)) {
+ lastParsedPos = tail;
+ return null;
+ }
+ String field = CodegenAccess.readObjectFieldAsString(iter);
+ value = iter.readAny();
+ cache.put(field, value);
+ if (field.hashCode() == target.hashCode() && field.equals(target)) {
+ lastParsedPos = CodegenAccess.head(iter);
+ return value;
+ }
+ }
+ while (CodegenAccess.nextToken(iter) == ',') {
+ String field = CodegenAccess.readObjectFieldAsString(iter);
+ value = iter.readAny();
+ cache.put(field, value);
+ if (field.hashCode() == target.hashCode() && field.equals(target)) {
+ lastParsedPos = CodegenAccess.head(iter);
+ return value;
+ }
}
+ lastParsedPos = tail;
+ return null;
+ } catch (IOException e) {
+ throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
- lastParsedPos = tail;
- return null;
}
private void fillCache() {
if (lastParsedPos == tail) {
return;
}
+ if (cache == null) {
+ cache = new HashMap(4);
+ }
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
try {
- JsonIterator iter = tlsIter.get();
iter.reset(data, lastParsedPos, tail);
- if (cache == null) {
- cache = new HashMap(4);
- }
- if (!CodegenAccess.readObjectStart(iter)) {
- lastParsedPos = tail;
- return;
+ if (lastParsedPos == head) {
+ if (!CodegenAccess.readObjectStart(iter)) {
+ lastParsedPos = tail;
+ return;
+ }
+ String field = CodegenAccess.readObjectFieldAsString(iter);
+ cache.put(field, iter.readAny());
}
- String field = CodegenAccess.readObjectFieldAsString(iter);
- cache.put(field, iter.readAny());
while (CodegenAccess.nextToken(iter) == ',') {
- field = CodegenAccess.readObjectFieldAsString(iter);
+ String field = CodegenAccess.readObjectFieldAsString(iter);
cache.put(field, iter.readAny());
}
lastParsedPos = tail;
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
+ }
+ }
+
+ @Override
+ public EntryIterator entries() {
+ return new LazyIterator();
+ }
+
+
+ private class LazyIterator implements EntryIterator {
+
+ private Iterator> mapIter;
+ private String key;
+ private Any value;
+
+ public LazyIterator() {
+ if (cache == null) {
+ cache = new HashMap();
+ }
+ mapIter = new HashMap(cache).entrySet().iterator();
+ try {
+ if (lastParsedPos == head) {
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
+ try {
+ iter.reset(data, lastParsedPos, tail);
+ if (!CodegenAccess.readObjectStart(iter)) {
+ lastParsedPos = tail;
+ } else {
+ lastParsedPos = CodegenAccess.head(iter);
+ }
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
+ }
+ }
+ } catch (IOException e) {
+ throw new JsonException(e);
+ }
+ }
+
+ @Override
+ public boolean next() {
+ if (lastParsedPos == tail) {
+ return false;
+ }
+ if (mapIter != null) {
+ if (mapIter.hasNext()) {
+ Map.Entry entry = mapIter.next();
+ key = entry.getKey();
+ value = entry.getValue();
+ return true;
+ } else {
+ mapIter = null;
+ }
+ }
+ JsonIterator iter = JsonIteratorPool.borrowJsonIterator();
+ try {
+ iter.reset(data, lastParsedPos, tail);
+ key = CodegenAccess.readObjectFieldAsString(iter);
+ value = iter.readAny();
+ cache.put(key, value);
+ if (CodegenAccess.nextToken(iter) == ',') {
+ lastParsedPos = CodegenAccess.head(iter);
+ } else {
+ lastParsedPos = tail;
+ }
+ } catch (IOException e) {
+ throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
+ }
+ return true;
+ }
+
+ @Override
+ public String key() {
+ return key;
+ }
+
+ @Override
+ public Any value() {
+ return value;
+ }
+ }
+
+ @Override
+ public void writeTo(JsonStream stream) throws IOException {
+ if (lastParsedPos == head) {
+ super.writeTo(stream);
+ } else {
+ // there might be modification
+ fillCache();
+ stream.writeVal(typeLiteral, (Map) cache);
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (lastParsedPos == head) {
+ return super.toString();
+ } else {
+ fillCache();
+ return JsonStream.serialize(cache);
}
}
}
diff --git a/src/main/java/com/jsoniter/any/StringAny.java b/src/main/java/com/jsoniter/any/StringAny.java
index db0412b1..ccd9c090 100644
--- a/src/main/java/com/jsoniter/any/StringAny.java
+++ b/src/main/java/com/jsoniter/any/StringAny.java
@@ -4,6 +4,8 @@
import com.jsoniter.output.JsonStream;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class StringAny extends Any {
@@ -77,6 +79,16 @@ public double toDouble() {
return Double.valueOf(val);
}
+ @Override
+ public BigInteger toBigInteger() {
+ return new BigInteger(val);
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return new BigDecimal(val);
+ }
+
@Override
public String toString() {
return val;
diff --git a/src/main/java/com/jsoniter/any/StringLazyAny.java b/src/main/java/com/jsoniter/any/StringLazyAny.java
index 02853653..4e9f3bab 100644
--- a/src/main/java/com/jsoniter/any/StringLazyAny.java
+++ b/src/main/java/com/jsoniter/any/StringLazyAny.java
@@ -1,11 +1,14 @@
package com.jsoniter.any;
import com.jsoniter.CodegenAccess;
-import com.jsoniter.JsonException;
import com.jsoniter.JsonIterator;
+import com.jsoniter.JsonIteratorPool;
import com.jsoniter.ValueType;
+import com.jsoniter.spi.JsonException;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class StringLazyAny extends LazyAny {
private final static String FALSE = "false";
@@ -52,48 +55,66 @@ public boolean toBoolean() {
@Override
public int toInt() {
+ JsonIterator iter = parse();
try {
- JsonIterator iter = parse();
CodegenAccess.nextToken(iter);
return iter.readInt();
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
@Override
public long toLong() {
+ JsonIterator iter = parse();
try {
- JsonIterator iter = parse();
CodegenAccess.nextToken(iter);
return iter.readLong();
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
@Override
public float toFloat() {
+ JsonIterator iter = parse();
try {
- JsonIterator iter = parse();
CodegenAccess.nextToken(iter);
return iter.readFloat();
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
@Override
public double toDouble() {
+ JsonIterator iter = parse();
try {
- JsonIterator iter = parse();
CodegenAccess.nextToken(iter);
return iter.readDouble();
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
+ @Override
+ public BigInteger toBigInteger() {
+ return new BigInteger(toString());
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return new BigDecimal(toString());
+ }
+
@Override
public String toString() {
fillCache();
@@ -102,10 +123,13 @@ public String toString() {
private void fillCache() {
if (cache == null) {
+ JsonIterator iter = parse();
try {
- cache = parse().readString();
+ cache = iter.readString();
} catch (IOException e) {
throw new JsonException();
+ } finally {
+ JsonIteratorPool.returnJsonIterator(iter);
}
}
}
diff --git a/src/main/java/com/jsoniter/any/TrueAny.java b/src/main/java/com/jsoniter/any/TrueAny.java
index 6163d0cd..2511f4f2 100644
--- a/src/main/java/com/jsoniter/any/TrueAny.java
+++ b/src/main/java/com/jsoniter/any/TrueAny.java
@@ -4,6 +4,8 @@
import com.jsoniter.output.JsonStream;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
class TrueAny extends Any {
@@ -44,6 +46,16 @@ public double toDouble() {
return 1;
}
+ @Override
+ public BigInteger toBigInteger() {
+ return BigInteger.ONE;
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ return BigDecimal.ONE;
+ }
+
@Override
public String toString() {
return "true";
diff --git a/src/main/java/com/jsoniter/extra/Base64.java b/src/main/java/com/jsoniter/extra/Base64.java
new file mode 100644
index 00000000..e09d910b
--- /dev/null
+++ b/src/main/java/com/jsoniter/extra/Base64.java
@@ -0,0 +1,257 @@
+package com.jsoniter.extra;
+
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Slice;
+import com.jsoniter.output.JsonStream;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance
+ * with RFC 2045.
+ * On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster
+ * on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes)
+ * compared to sun.misc.Encoder()/Decoder().
+ *
+ * On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and
+ * about 50% faster for decoding large arrays. This implementation is about twice as fast on very small
+ * arrays (< 30 bytes). If source/destination is a String this
+ * version is about three times as fast due to the fact that the Commons Codec result has to be recoded
+ * to a String from byte[], which is very expensive.
+ *
+ * This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only
+ * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice
+ * as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown
+ * whether Sun's sun.misc.Encoder()/Decoder() produce temporary arrays but since performance
+ * is quite low it probably does.
+ *
+ * The encoder produces the same output as the Sun one except that the Sun's encoder appends
+ * a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the
+ * length and is probably a side effect. Both are in conformance with RFC 2045 though.
+ * Commons codec seem to always att a trailing line separator.
+ *
+ * Note!
+ * The encode/decode method pairs (types) come in three versions with the exact same algorithm and
+ * thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different
+ * format types. The methods not used can simply be commented out.
+ *
+ * There is also a "fast" version of all decode methods that works the same way as the normal ones, but
+ * har a few demands on the decoded input. Normally though, these fast verions should be used if the source if
+ * the input is known and it hasn't bee tampered with.
+ *
+ * If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com.
+ *
+ * Licence (BSD):
+ * ==============
+ *
+ * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this list
+ * of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ * Neither the name of the MiG InfoCom AB nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * @version 2.2
+ * @author Mikael Grev
+ * Date: 2004-aug-02
+ * Time: 11:31:11
+ */
+
+abstract class Base64 {
+ private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+ static final byte[] BA;
+ static final int[] IA = new int[256];
+ static {
+ Arrays.fill(IA, -1);
+ for (int i = 0, iS = CA.length; i < iS; i++) {
+ IA[CA[i]] = i;
+ }
+ IA['='] = 0;
+ BA = new byte[CA.length];
+ for (int i = 0; i < CA.length; i++) {
+ BA[i] = (byte)CA[i];
+ }
+ }
+
+ static int encodeToChar(byte[] sArr, char[] dArr, final int start) {
+ final int sLen = sArr.length;
+
+ final int eLen = (sLen / 3) * 3; // Length of even 24-bits.
+ final int dLen = ((sLen - 1) / 3 + 1) << 2; // Returned character count
+
+ // Encode even 24-bits
+ for (int s = 0, d = start; s < eLen;) {
+ // Copy next three bytes into lower 24 bits of int, paying attension to sign.
+ int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
+
+ // Encode the int into four chars
+ dArr[d++] = CA[(i >>> 18) & 0x3f];
+ dArr[d++] = CA[(i >>> 12) & 0x3f];
+ dArr[d++] = CA[(i >>> 6) & 0x3f];
+ dArr[d++] = CA[i & 0x3f];
+ }
+
+ // Pad and encode last bits if source isn't even 24 bits.
+ int left = sLen - eLen; // 0 - 2.
+ if (left > 0) {
+ // Prepare the int
+ int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
+
+ // Set last four chars
+ dArr[start + dLen - 4] = CA[i >> 12];
+ dArr[start + dLen - 3] = CA[(i >>> 6) & 0x3f];
+ dArr[start + dLen - 2] = left == 2 ? CA[i & 0x3f] : '=';
+ dArr[start + dLen - 1] = '=';
+ }
+
+ return dLen;
+ }
+
+ static int encodeToBytes(byte[] sArr, JsonStream stream) throws IOException {
+ final int sLen = sArr.length;
+
+ final int eLen = (sLen / 3) * 3; // Length of even 24-bits.
+ final int dLen = ((sLen - 1) / 3 + 1) << 2; // Returned character count
+
+ // Encode even 24-bits
+ for (int s = 0; s < eLen;) {
+ // Copy next three bytes into lower 24 bits of int, paying attension to sign.
+ int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
+
+ // Encode the int into four chars
+ stream.write(BA[(i >>> 18) & 0x3f], BA[(i >>> 12) & 0x3f], BA[(i >>> 6) & 0x3f], BA[i & 0x3f]);
+ }
+
+ // Pad and encode last bits if source isn't even 24 bits.
+ int left = sLen - eLen; // 0 - 2.
+ if (left > 0) {
+ // Prepare the int
+ int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
+
+ // Set last four chars
+ stream.write(BA[i >> 12], BA[(i >>> 6) & 0x3f], left == 2 ? BA[i & 0x3f] : (byte)'=', (byte)'=');
+ }
+
+ return dLen;
+ }
+
+ static void encodeLongBits(long bits, JsonStream stream) throws IOException {
+ int i = (int) bits;
+ byte b1 = BA[(i >>> 18) & 0x3f];
+ byte b2 = BA[(i >>> 12) & 0x3f];
+ byte b3 = BA[(i >>> 6) & 0x3f];
+ byte b4 = BA[i & 0x3f];
+ stream.write((byte)'"', b1, b2, b3, b4);
+ bits = bits >>> 24;
+ i = (int) bits;
+ b1 = BA[(i >>> 18) & 0x3f];
+ b2 = BA[(i >>> 12) & 0x3f];
+ b3 = BA[(i >>> 6) & 0x3f];
+ b4 = BA[i & 0x3f];
+ stream.write(b1, b2, b3, b4);
+ bits = (bits >>> 24) << 2;
+ i = (int) bits;
+ b1 = BA[i >> 12];
+ b2 = BA[(i >>> 6) & 0x3f];
+ b3 = BA[i & 0x3f];
+ stream.write(b1, b2, b3, (byte)'"');
+ }
+
+ static long decodeLongBits(JsonIterator iter) throws IOException {
+ Slice slice = iter.readStringAsSlice();
+ if (slice.len() != 11) {
+ throw iter.reportError("decodeLongBits", "must be 11 bytes for long bits encoded double");
+ }
+ byte[] encoded = slice.data();
+ int sIx = slice.head();
+ long i = IA[encoded[sIx++]] << 18 | IA[encoded[sIx++]] << 12 | IA[encoded[sIx++]] << 6 | IA[encoded[sIx++]];
+ long bits = i;
+ i = IA[encoded[sIx++]] << 18 | IA[encoded[sIx++]] << 12 | IA[encoded[sIx++]] << 6 | IA[encoded[sIx++]];
+ bits = i << 24 | bits;
+ i = IA[encoded[sIx++]] << 12 | IA[encoded[sIx++]] << 6 | IA[encoded[sIx]];
+ bits = i << 46 | bits;
+ return bits;
+ }
+
+ static int findEnd(final byte[] sArr, final int start) {
+ for (int i = start; i < sArr.length; i++)
+ if (IA[sArr[i] & 0xff] < 0)
+ return i;
+ return sArr.length;
+ }
+
+ private final static byte[] EMPTY_ARRAY = new byte[0];
+
+ static byte[] decodeFast(final byte[] sArr, final int start, final int end) {
+ // Check special case
+ int sLen = end - start;
+ if (sLen == 0)
+ return EMPTY_ARRAY;
+
+ int sIx = start, eIx = end - 1; // Start and end index after trimming.
+
+ // Trim illegal chars from start
+ while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0)
+ sIx++;
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0)
+ eIx--;
+
+ // get the padding count (=) (0, 1 or 2)
+ int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
+ int cCnt = eIx - sIx + 1; // Content count including possible separators
+ int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
+
+ int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // Decode last 1-3 bytes (incl '=') into 1-3 bytes
+ int i = 0;
+ for (int j = 0; sIx <= eIx - pad; j++)
+ i |= IA[sArr[sIx++]] << (18 - j * 6);
+
+ for (int r = 16; d < len; r -= 8)
+ dArr[d++] = (byte) (i >> r);
+ }
+
+ return dArr;
+ }
+}
diff --git a/src/main/java/com/jsoniter/extra/Base64FloatSupport.java b/src/main/java/com/jsoniter/extra/Base64FloatSupport.java
new file mode 100644
index 00000000..b2fdf732
--- /dev/null
+++ b/src/main/java/com/jsoniter/extra/Base64FloatSupport.java
@@ -0,0 +1,224 @@
+package com.jsoniter.extra;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Slice;
+import com.jsoniter.any.Any;
+import com.jsoniter.output.JsonStream;
+import com.jsoniter.spi.Decoder;
+import com.jsoniter.spi.Encoder;
+import com.jsoniter.spi.JsonException;
+import com.jsoniter.spi.JsoniterSpi;
+
+import java.io.IOException;
+
+/**
+ * encode float/double as base64, faster than PreciseFloatSupport
+ */
+public class Base64FloatSupport {
+
+ final static int[] DIGITS = new int[256];
+ final static int[] HEX = new int[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ final static int[] DEC = new int[127];
+
+ static {
+ for (int i = 0; i < 256; i++) {
+ DIGITS[i] = HEX[i >> 4] << 8 | HEX[i & 0xf];
+ }
+ DEC['0'] = 0;
+ DEC['1'] = 1;
+ DEC['2'] = 2;
+ DEC['3'] = 3;
+ DEC['4'] = 4;
+ DEC['5'] = 5;
+ DEC['6'] = 6;
+ DEC['7'] = 7;
+ DEC['8'] = 8;
+ DEC['9'] = 9;
+ DEC['a'] = 10;
+ DEC['b'] = 11;
+ DEC['c'] = 12;
+ DEC['d'] = 13;
+ DEC['e'] = 14;
+ DEC['f'] = 15;
+ }
+
+ private static boolean enabled;
+
+ public static synchronized void enableEncodersAndDecoders() {
+ if (enabled) {
+ throw new JsonException("BinaryFloatSupport.enable can only be called once");
+ }
+ enabled = true;
+ enableDecoders();
+ JsoniterSpi.registerTypeEncoder(Double.class, new Encoder.ReflectionEncoder() {
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ Double number = (Double) obj;
+ long bits = Double.doubleToRawLongBits(number.doubleValue());
+ Base64.encodeLongBits(bits, stream);
+ }
+
+ @Override
+ public Any wrap(Object obj) {
+ Double number = (Double) obj;
+ return Any.wrap(number.doubleValue());
+ }
+ });
+ JsoniterSpi.registerTypeEncoder(double.class, new Encoder.DoubleEncoder() {
+ @Override
+ public void encodeDouble(double obj, JsonStream stream) throws IOException {
+ long bits = Double.doubleToRawLongBits(obj);
+ Base64.encodeLongBits(bits, stream);
+ }
+ });
+ JsoniterSpi.registerTypeEncoder(Float.class, new Encoder.ReflectionEncoder() {
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ Float number = (Float) obj;
+ long bits = Double.doubleToRawLongBits(number.doubleValue());
+ Base64.encodeLongBits(bits, stream);
+ }
+
+ @Override
+ public Any wrap(Object obj) {
+ Float number = (Float) obj;
+ return Any.wrap(number.floatValue());
+ }
+ });
+ JsoniterSpi.registerTypeEncoder(float.class, new Encoder.FloatEncoder() {
+ @Override
+ public void encodeFloat(float obj, JsonStream stream) throws IOException {
+ long bits = Double.doubleToRawLongBits(obj);
+ Base64.encodeLongBits(bits, stream);
+ }
+ });
+ }
+
+ public static void enableDecoders() {
+ JsoniterSpi.registerTypeDecoder(Double.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ byte token = CodegenAccess.nextToken(iter);
+ CodegenAccess.unreadByte(iter);
+ if (token == '"') {
+ return Double.longBitsToDouble(Base64.decodeLongBits(iter));
+ } else {
+ return iter.readDouble();
+ }
+ }
+ });
+ JsoniterSpi.registerTypeDecoder(double.class, new Decoder.DoubleDecoder() {
+ @Override
+ public double decodeDouble(JsonIterator iter) throws IOException {
+ byte token = CodegenAccess.nextToken(iter);
+ CodegenAccess.unreadByte(iter);
+ if (token == '"') {
+ return Double.longBitsToDouble(Base64.decodeLongBits(iter));
+ }else {
+ return iter.readDouble();
+ }
+ }
+ });
+ JsoniterSpi.registerTypeDecoder(Float.class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ byte token = CodegenAccess.nextToken(iter);
+ CodegenAccess.unreadByte(iter);
+ if (token == '"') {
+ return (float)Double.longBitsToDouble(Base64.decodeLongBits(iter));
+ }else {
+ return (float)iter.readDouble();
+ }
+ }
+ });
+ JsoniterSpi.registerTypeDecoder(float.class, new Decoder.FloatDecoder() {
+ @Override
+ public float decodeFloat(JsonIterator iter) throws IOException {
+ byte token = CodegenAccess.nextToken(iter);
+ CodegenAccess.unreadByte(iter);
+ if (token == '"') {
+ return (float)Double.longBitsToDouble(Base64.decodeLongBits(iter));
+ }else {
+ return (float)iter.readDouble();
+ }
+ }
+ });
+ }
+
+ private static long readLongBits(JsonIterator iter) throws IOException {
+ Slice slice = iter.readStringAsSlice();
+ byte[] data = slice.data();
+ long val = 0;
+ for (int i = slice.head(); i < slice.tail(); i++) {
+ byte b = data[i];
+ val = val << 4 | DEC[b];
+ }
+ return val;
+ }
+
+ private static void writeLongBits(long bits, JsonStream stream) throws IOException {
+ int digit = DIGITS[(int) (bits & 0xff)];
+ byte b2 = (byte) (digit >> 8);
+ byte b1 = (byte) digit;
+ bits = bits >> 8;
+ if (bits == 0) {
+ stream.write((byte) '"', b2, b1, (byte) '"');
+ }
+ digit = DIGITS[(int) (bits & 0xff)];
+ byte b4 = (byte) (digit >> 8);
+ byte b3 = (byte) digit;
+ bits = bits >> 8;
+ if (bits == 0) {
+ stream.write((byte) '"', b4, b3, b2, b1, (byte) '"');
+ }
+ digit = DIGITS[(int) (bits & 0xff)];
+ byte b6 = (byte) (digit >> 8);
+ byte b5 = (byte) digit;
+ bits = bits >> 8;
+ if (bits == 0) {
+ stream.write((byte) '"', b6, b5, b4, b3);
+ stream.write(b2, b1, (byte) '"');
+ }
+ digit = DIGITS[(int) (bits & 0xff)];
+ byte b8 = (byte) (digit >> 8);
+ byte b7 = (byte) digit;
+ bits = bits >> 8;
+ if (bits == 0) {
+ stream.write((byte) '"', b8, b7, b6, b5, b4);
+ stream.write(b3, b2, b1, (byte) '"');
+ }
+ digit = DIGITS[(int) (bits & 0xff)];
+ byte b10 = (byte) (digit >> 8);
+ byte b9 = (byte) digit;
+ bits = bits >> 8;
+ if (bits == 0) {
+ stream.write((byte) '"', b10, b9, b8, b7, b6);
+ stream.write(b5, b4, b3, b2, b1, (byte) '"');
+ }
+ digit = DIGITS[(int) (bits & 0xff)];
+ byte b12 = (byte) (digit >> 8);
+ byte b11 = (byte) digit;
+ bits = bits >> 8;
+ if (bits == 0) {
+ stream.write((byte) '"', b12, b11, b10, b9, b8);
+ stream.write(b7, b6, b5, b4, b3, b2);
+ stream.write(b1, (byte) '"');
+ }
+ digit = DIGITS[(int) (bits & 0xff)];
+ byte b14 = (byte) (digit >> 8);
+ byte b13 = (byte) digit;
+ bits = bits >> 8;
+ if (bits == 0) {
+ stream.write((byte) '"', b14, b13, b12, b11, b10);
+ stream.write(b9, b8, b7, b6, b5, b4);
+ stream.write(b3, b2, b1, (byte) '"');
+ }
+ digit = DIGITS[(int) (bits & 0xff)];
+ byte b16 = (byte) (digit >> 8);
+ byte b15 = (byte) digit;
+ stream.write((byte) '"', b16, b15, b14, b13, b12);
+ stream.write(b11, b10, b9, b8, b7, b6);
+ stream.write(b5, b4, b3, b2, b1, (byte) '"');
+ }
+}
diff --git a/src/main/java/com/jsoniter/extra/Base64Support.java b/src/main/java/com/jsoniter/extra/Base64Support.java
new file mode 100644
index 00000000..676178f7
--- /dev/null
+++ b/src/main/java/com/jsoniter/extra/Base64Support.java
@@ -0,0 +1,40 @@
+package com.jsoniter.extra;
+
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Slice;
+import com.jsoniter.output.JsonStream;
+import com.jsoniter.spi.Decoder;
+import com.jsoniter.spi.Encoder;
+import com.jsoniter.spi.JsonException;
+import com.jsoniter.spi.JsoniterSpi;
+
+import java.io.IOException;
+
+/**
+ * byte[] <=> base64
+ */
+public class Base64Support {
+ private static boolean enabled;
+ public static synchronized void enable() {
+ if (enabled) {
+ throw new JsonException("Base64Support.enable can only be called once");
+ }
+ enabled = true;
+ JsoniterSpi.registerTypeDecoder(byte[].class, new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ Slice slice = iter.readStringAsSlice();
+ return Base64.decodeFast(slice.data(), slice.head(), slice.tail());
+ }
+ });
+ JsoniterSpi.registerTypeEncoder(byte[].class, new Encoder() {
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ byte[] bytes = (byte[]) obj;
+ stream.write('"');
+ Base64.encodeToBytes(bytes, stream);
+ stream.write('"');
+ }
+ });
+ }
+}
diff --git a/src/main/java/com/jsoniter/extra/GsonCompatibilityMode.java b/src/main/java/com/jsoniter/extra/GsonCompatibilityMode.java
new file mode 100644
index 00000000..7d6b6a63
--- /dev/null
+++ b/src/main/java/com/jsoniter/extra/GsonCompatibilityMode.java
@@ -0,0 +1,611 @@
+package com.jsoniter.extra;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.FieldNamingStrategy;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.annotations.Since;
+import com.google.gson.annotations.Until;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.ValueType;
+import com.jsoniter.annotation.JsonIgnore;
+import com.jsoniter.annotation.JsonProperty;
+import com.jsoniter.output.JsonStream;
+import com.jsoniter.spi.*;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+public class GsonCompatibilityMode extends Config {
+
+ private final static int SURR1_FIRST = 0xD800;
+ private final static int SURR1_LAST = 0xDBFF;
+ private final static int SURR2_FIRST = 0xDC00;
+ private final static int SURR2_LAST = 0xDFFF;
+ private static final String[] REPLACEMENT_CHARS;
+ private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
+
+ static {
+ REPLACEMENT_CHARS = new String[128];
+ for (int i = 0; i <= 0x1f; i++) {
+ REPLACEMENT_CHARS[i] = String.format("\\u%04x", (int) i);
+ }
+ REPLACEMENT_CHARS['"'] = "\\\"";
+ REPLACEMENT_CHARS['\\'] = "\\\\";
+ REPLACEMENT_CHARS['\t'] = "\\t";
+ REPLACEMENT_CHARS['\b'] = "\\b";
+ REPLACEMENT_CHARS['\n'] = "\\n";
+ REPLACEMENT_CHARS['\r'] = "\\r";
+ REPLACEMENT_CHARS['\f'] = "\\f";
+ HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone();
+ HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c";
+ HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e";
+ HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026";
+ HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d";
+ HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027";
+ }
+
+ private GsonCompatibilityMode(String configName, Builder builder) {
+ super(configName, builder);
+ }
+
+ protected Builder builder() {
+ return (Builder) super.builder();
+ }
+
+ public static class Builder extends Config.Builder {
+ private boolean excludeFieldsWithoutExposeAnnotation = false;
+ private boolean disableHtmlEscaping = false;
+ private ThreadLocal dateFormat = new ThreadLocal() {
+ @Override
+ protected DateFormat initialValue() {
+ return DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US);
+ }
+ };
+ private FieldNamingStrategy fieldNamingStrategy;
+ private Double version;
+ private Set serializationExclusionStrategies = new HashSet();
+ private Set deserializationExclusionStrategies = new HashSet();
+
+ public Builder() {
+ omitDefaultValue(true);
+ }
+
+ public Builder excludeFieldsWithoutExposeAnnotation() {
+ excludeFieldsWithoutExposeAnnotation = true;
+ return this;
+ }
+
+ public Builder serializeNulls() {
+ omitDefaultValue(false);
+ return this;
+ }
+
+ public Builder setDateFormat(int dateStyle) {
+ // no op, same as gson
+ return this;
+ }
+
+ public Builder setDateFormat(final int dateStyle, final int timeStyle) {
+ dateFormat = new ThreadLocal() {
+ @Override
+ protected DateFormat initialValue() {
+ return DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US);
+ }
+ };
+ return this;
+ }
+
+ public Builder setDateFormat(final String pattern) {
+ dateFormat = new ThreadLocal() {
+ @Override
+ protected DateFormat initialValue() {
+ return new SimpleDateFormat(pattern, Locale.US);
+ }
+ };
+ return this;
+ }
+
+ public Builder setFieldNamingStrategy(FieldNamingStrategy fieldNamingStrategy) {
+ this.fieldNamingStrategy = fieldNamingStrategy;
+ return this;
+ }
+
+ public Builder setFieldNamingPolicy(FieldNamingPolicy namingConvention) {
+ this.fieldNamingStrategy = namingConvention;
+ return this;
+ }
+
+ public Builder setPrettyPrinting() {
+ indentionStep(2);
+ return this;
+ }
+
+ public Builder disableHtmlEscaping() {
+ disableHtmlEscaping = true;
+ return this;
+ }
+
+ public Builder setVersion(double version) {
+ this.version = version;
+ return this;
+ }
+
+ public Builder setExclusionStrategies(ExclusionStrategy... strategies) {
+ for (ExclusionStrategy strategy : strategies) {
+ addSerializationExclusionStrategy(strategy);
+ }
+ return this;
+ }
+
+ public Builder addSerializationExclusionStrategy(ExclusionStrategy exclusionStrategy) {
+ serializationExclusionStrategies.add(exclusionStrategy);
+ return this;
+ }
+
+ public Builder addDeserializationExclusionStrategy(ExclusionStrategy exclusionStrategy) {
+ deserializationExclusionStrategies.add(exclusionStrategy);
+ return this;
+ }
+
+ public GsonCompatibilityMode build() {
+ escapeUnicode(false);
+ return (GsonCompatibilityMode) super.build();
+ }
+
+ @Override
+ protected Config doBuild(String configName) {
+ return new GsonCompatibilityMode(configName, this);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ Builder builder = (Builder) o;
+
+ return excludeFieldsWithoutExposeAnnotation == builder.excludeFieldsWithoutExposeAnnotation &&
+ disableHtmlEscaping == builder.disableHtmlEscaping &&
+ dateFormat.get().equals(builder.dateFormat.get()) &&
+ (fieldNamingStrategy != null ? fieldNamingStrategy.equals(builder.fieldNamingStrategy) :
+ builder.fieldNamingStrategy == null) &&
+ (version != null ? version.equals(builder.version) : builder.version == null) &&
+ (serializationExclusionStrategies != null ?
+ serializationExclusionStrategies.equals(builder.serializationExclusionStrategies) :
+ builder.serializationExclusionStrategies == null) &&
+ (deserializationExclusionStrategies != null ?
+ deserializationExclusionStrategies.equals(builder.deserializationExclusionStrategies) :
+ builder.deserializationExclusionStrategies == null);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (excludeFieldsWithoutExposeAnnotation ? 1 : 0);
+ result = 31 * result + (disableHtmlEscaping ? 1 : 0);
+ result = 31 * result + dateFormat.get().hashCode();
+ result = 31 * result + (fieldNamingStrategy != null ? fieldNamingStrategy.hashCode() : 0);
+ result = 31 * result + (version != null ? version.hashCode() : 0);
+ result = 31 * result + (serializationExclusionStrategies != null ? serializationExclusionStrategies.hashCode() : 0);
+ result = 31 * result + (deserializationExclusionStrategies != null ? deserializationExclusionStrategies.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public Config.Builder copy() {
+ Builder copied = (Builder) super.copy();
+ copied.excludeFieldsWithoutExposeAnnotation = excludeFieldsWithoutExposeAnnotation;
+ copied.disableHtmlEscaping = disableHtmlEscaping;
+ copied.dateFormat = dateFormat;
+ copied.fieldNamingStrategy = fieldNamingStrategy;
+ copied.version = version;
+ copied.serializationExclusionStrategies = new HashSet(serializationExclusionStrategies);
+ copied.deserializationExclusionStrategies = new HashSet(deserializationExclusionStrategies);
+ return copied;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " => GsonCompatibilityMode{" +
+ "excludeFieldsWithoutExposeAnnotation=" + excludeFieldsWithoutExposeAnnotation +
+ ", disableHtmlEscaping=" + disableHtmlEscaping +
+ ", dateFormat=" + dateFormat +
+ ", fieldNamingStrategy=" + fieldNamingStrategy +
+ ", version=" + version +
+ ", serializationExclusionStrategies=" + serializationExclusionStrategies +
+ ", deserializationExclusionStrategies=" + deserializationExclusionStrategies +
+ '}';
+ }
+ }
+
+ @Override
+ protected OmitValue createOmitValue(Type valueType) {
+ if (valueType instanceof Class) {
+ Class clazz = (Class) valueType;
+ if (clazz.isPrimitive()) {
+ return null; // gson do not omit primitive zero
+ }
+ }
+ return super.createOmitValue(valueType);
+ }
+
+ @Override
+ public Encoder createEncoder(String cacheKey, Type type) {
+ if (Date.class == type) {
+ return new Encoder() {
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ DateFormat dateFormat = builder().dateFormat.get();
+ stream.writeVal(dateFormat.format(obj));
+ }
+ };
+ } else if (String.class == type) {
+ final String[] replacements;
+ if (builder().disableHtmlEscaping) {
+ replacements = REPLACEMENT_CHARS;
+ } else {
+ replacements = HTML_SAFE_REPLACEMENT_CHARS;
+ }
+ return new Encoder() {
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ String value = (String) obj;
+ stream.write('"');
+ int _surrogate;
+ for (int i = 0; i < value.length(); i++) {
+ int c = value.charAt(i);
+ String replacement;
+ if (c < 128) {
+ replacement = replacements[c];
+ if (replacement == null) {
+ stream.write(c);
+ } else {
+ stream.writeRaw(replacement);
+ }
+ } else if (c == '\u2028') {
+ stream.writeRaw("\\u2028");
+ } else if (c == '\u2029') {
+ stream.writeRaw("\\u2029");
+ } else {
+ if (c < 0x800) { // 2-byte
+ stream.write(
+ (byte) (0xc0 | (c >> 6)),
+ (byte) (0x80 | (c & 0x3f))
+ );
+ } else { // 3 or 4 bytes
+ // Surrogates?
+ if (c < SURR1_FIRST || c > SURR2_LAST) {
+ stream.write(
+ (byte) (0xe0 | (c >> 12)),
+ (byte) (0x80 | ((c >> 6) & 0x3f)),
+ (byte) (0x80 | (c & 0x3f))
+ );
+ continue;
+ }
+ // Yup, a surrogate:
+ if (c > SURR1_LAST) { // must be from first range
+ throw new JsonException("illegalSurrogate");
+ }
+ _surrogate = c;
+ // and if so, followed by another from next range
+ if (i >= value.length()) { // unless we hit the end?
+ break;
+ }
+ i++;
+ c = value.charAt(i);
+ int firstPart = _surrogate;
+ _surrogate = 0;
+ // Ok, then, is the second part valid?
+ if (c < SURR2_FIRST || c > SURR2_LAST) {
+ throw new JsonException("Broken surrogate pair: first char 0x" + Integer.toHexString(firstPart) + ", second 0x" + Integer.toHexString(c) + "; illegal combination");
+ }
+ c = 0x10000 + ((firstPart - SURR1_FIRST) << 10) + (c - SURR2_FIRST);
+ if (c > 0x10FFFF) { // illegal in JSON as well as in XML
+ throw new JsonException("illegalSurrogate");
+ }
+ stream.write(
+ (byte) (0xf0 | (c >> 18)),
+ (byte) (0x80 | ((c >> 12) & 0x3f)),
+ (byte) (0x80 | ((c >> 6) & 0x3f)),
+ (byte) (0x80 | (c & 0x3f))
+ );
+ }
+ }
+ }
+ stream.write('"');
+ }
+ };
+ }
+ return super.createEncoder(cacheKey, type);
+ }
+
+ @Override
+ public Decoder createDecoder(String cacheKey, Type type) {
+ if (Date.class == type) {
+ return new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ DateFormat dateFormat = builder().dateFormat.get();
+ try {
+ String input = iter.readString();
+ return dateFormat.parse(input);
+ } catch (ParseException e) {
+ throw new JsonException(e);
+ }
+ }
+ };
+ } else if (String.class == type) {
+ return new Decoder() {
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ ValueType valueType = iter.whatIsNext();
+ if (valueType == ValueType.STRING) {
+ return iter.readString();
+ } else if (valueType == ValueType.NUMBER) {
+ return iter.readNumberAsString();
+ } else if (valueType == ValueType.BOOLEAN) {
+ return iter.readBoolean() ? "true" : "false";
+ } else if (valueType == ValueType.NULL) {
+ iter.skip();
+ return null;
+ } else {
+ throw new JsonException("expect string, but found " + valueType);
+ }
+ }
+ };
+ } else if (boolean.class == type) {
+ return new Decoder.BooleanDecoder() {
+ @Override
+ public boolean decodeBoolean(JsonIterator iter) throws IOException {
+ ValueType valueType = iter.whatIsNext();
+ if (valueType == ValueType.BOOLEAN) {
+ return iter.readBoolean();
+ } else if (valueType == ValueType.NULL) {
+ iter.skip();
+ return false;
+ } else {
+ throw new JsonException("expect boolean, but found " + valueType);
+ }
+ }
+ };
+ } else if (long.class == type) {
+ return new Decoder.LongDecoder() {
+ @Override
+ public long decodeLong(JsonIterator iter) throws IOException {
+ ValueType valueType = iter.whatIsNext();
+ if (valueType == ValueType.NUMBER) {
+ return iter.readLong();
+ } else if (valueType == ValueType.NULL) {
+ iter.skip();
+ return 0;
+ } else {
+ throw new JsonException("expect long, but found " + valueType);
+ }
+ }
+ };
+ } else if (int.class == type) {
+ return new Decoder.IntDecoder() {
+ @Override
+ public int decodeInt(JsonIterator iter) throws IOException {
+ ValueType valueType = iter.whatIsNext();
+ if (valueType == ValueType.NUMBER) {
+ return iter.readInt();
+ } else if (valueType == ValueType.NULL) {
+ iter.skip();
+ return 0;
+ } else {
+ throw new JsonException("expect int, but found " + valueType);
+ }
+ }
+ };
+ } else if (float.class == type) {
+ return new Decoder.FloatDecoder() {
+ @Override
+ public float decodeFloat(JsonIterator iter) throws IOException {
+ ValueType valueType = iter.whatIsNext();
+ if (valueType == ValueType.NUMBER) {
+ return iter.readFloat();
+ } else if (valueType == ValueType.NULL) {
+ iter.skip();
+ return 0.0f;
+ } else {
+ throw new JsonException("expect float, but found " + valueType);
+ }
+ }
+ };
+ } else if (double.class == type) {
+ return new Decoder.DoubleDecoder() {
+ @Override
+ public double decodeDouble(JsonIterator iter) throws IOException {
+ ValueType valueType = iter.whatIsNext();
+ if (valueType == ValueType.NUMBER) {
+ return iter.readDouble();
+ } else if (valueType == ValueType.NULL) {
+ iter.skip();
+ return 0.0d;
+ } else {
+ throw new JsonException("expect float, but found " + valueType);
+ }
+ }
+ };
+ }
+ return super.createDecoder(cacheKey, type);
+ }
+
+ @Override
+ public void updateClassDescriptor(ClassDescriptor desc) {
+ FieldNamingStrategy fieldNamingStrategy = builder().fieldNamingStrategy;
+ for (Binding binding : desc.allBindings()) {
+ if (binding.method != null) {
+ binding.toNames = new String[0];
+ binding.fromNames = new String[0];
+ }
+ if (fieldNamingStrategy != null && binding.field != null) {
+ String translated = fieldNamingStrategy.translateName(binding.field);
+ binding.toNames = new String[]{translated};
+ binding.fromNames = new String[]{translated};
+ }
+ if (builder().version != null) {
+ Since since = binding.getAnnotation(Since.class);
+ if (since != null && builder().version < since.value()) {
+ binding.toNames = new String[0];
+ binding.fromNames = new String[0];
+ }
+ Until until = binding.getAnnotation(Until.class);
+ if (until != null && builder().version >= until.value()) {
+ binding.toNames = new String[0];
+ binding.fromNames = new String[0];
+ }
+ }
+ for (ExclusionStrategy strategy : builder().serializationExclusionStrategies) {
+ if (strategy.shouldSkipClass(binding.clazz)) {
+ binding.toNames = new String[0];
+ continue;
+ }
+ if (strategy.shouldSkipField(new FieldAttributes(binding.field))) {
+ binding.toNames = new String[0];
+ }
+ }
+ for (ExclusionStrategy strategy : builder().deserializationExclusionStrategies) {
+ if (strategy.shouldSkipClass(binding.clazz)) {
+ binding.fromNames = new String[0];
+ continue;
+ }
+ if (strategy.shouldSkipField(new FieldAttributes(binding.field))) {
+ binding.fromNames = new String[0];
+ }
+ }
+ }
+ super.updateClassDescriptor(desc);
+ }
+
+ @Override
+ protected JsonProperty getJsonProperty(Annotation[] annotations) {
+ JsonProperty jsoniterObj = super.getJsonProperty(annotations);
+ if (jsoniterObj != null) {
+ return jsoniterObj;
+ }
+ final SerializedName gsonObj = getAnnotation(
+ annotations, SerializedName.class);
+ if (gsonObj == null) {
+ return null;
+ }
+ return new JsonProperty() {
+
+ @Override
+ public String value() {
+ return "";
+ }
+
+ @Override
+ public String[] from() {
+ return new String[]{gsonObj.value()};
+ }
+
+ @Override
+ public String[] to() {
+ return new String[]{gsonObj.value()};
+ }
+
+ @Override
+ public boolean required() {
+ return false;
+ }
+
+ @Override
+ public Class extends Decoder> decoder() {
+ return Decoder.class;
+ }
+
+ @Override
+ public Class> implementation() {
+ return Object.class;
+ }
+
+ @Override
+ public Class extends Encoder> encoder() {
+ return Encoder.class;
+ }
+
+ @Override
+ public boolean nullable() {
+ return true;
+ }
+
+ @Override
+ public boolean collectionValueNullable() {
+ return true;
+ }
+
+ @Override
+ public String defaultValueToOmit() {
+ return "";
+ }
+
+ @Override
+ public Class extends Annotation> annotationType() {
+ return JsonProperty.class;
+ }
+ };
+ }
+
+ @Override
+ protected JsonIgnore getJsonIgnore(Annotation[] annotations) {
+
+ JsonIgnore jsoniterObj = super.getJsonIgnore(annotations);
+ if (jsoniterObj != null) {
+ return jsoniterObj;
+ }
+ if (builder().excludeFieldsWithoutExposeAnnotation) {
+ final Expose gsonObj = getAnnotation(
+ annotations, Expose.class);
+ if (gsonObj != null) {
+ return new JsonIgnore() {
+ @Override
+ public boolean ignoreDecoding() {
+ return !gsonObj.deserialize();
+ }
+
+ @Override
+ public boolean ignoreEncoding() {
+ return !gsonObj.serialize();
+ }
+
+ @Override
+ public Class extends Annotation> annotationType() {
+ return JsonIgnore.class;
+ }
+ };
+ }
+ return new JsonIgnore() {
+ @Override
+ public boolean ignoreDecoding() {
+ return true;
+ }
+
+ @Override
+ public boolean ignoreEncoding() {
+ return true;
+ }
+
+ @Override
+ public Class extends Annotation> annotationType() {
+ return JsonIgnore.class;
+ }
+ };
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/jsoniter/annotation/JacksonAnnotationSupport.java b/src/main/java/com/jsoniter/extra/JacksonCompatibilityMode.java
similarity index 51%
rename from src/main/java/com/jsoniter/annotation/JacksonAnnotationSupport.java
rename to src/main/java/com/jsoniter/extra/JacksonCompatibilityMode.java
index a1d09233..1935b3c1 100644
--- a/src/main/java/com/jsoniter/annotation/JacksonAnnotationSupport.java
+++ b/src/main/java/com/jsoniter/extra/JacksonCompatibilityMode.java
@@ -1,15 +1,34 @@
-package com.jsoniter.annotation;
+package com.jsoniter.extra;
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.jsoniter.annotation.*;
import com.jsoniter.spi.Decoder;
import com.jsoniter.spi.Encoder;
-import com.jsoniter.spi.JsoniterSpi;
+import com.jsoniter.spi.Config;
import java.lang.annotation.Annotation;
-public class JacksonAnnotationSupport extends JsoniterAnnotationSupport {
+public class JacksonCompatibilityMode extends Config {
- public static void enable() {
- JsoniterSpi.registerExtension(new JacksonAnnotationSupport());
+ public static class Builder extends Config.Builder {
+ public JacksonCompatibilityMode build() {
+ return (JacksonCompatibilityMode) super.build();
+ }
+
+ @Override
+ protected Config doBuild(String configName) {
+ return new JacksonCompatibilityMode(configName, this);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " => JacksonCompatibilityMode{}";
+ }
+ }
+
+ private JacksonCompatibilityMode(String configName, Builder builder) {
+ super(configName, builder);
}
@Override
@@ -25,7 +44,12 @@ protected JsonIgnore getJsonIgnore(Annotation[] annotations) {
}
return new JsonIgnore() {
@Override
- public boolean value() {
+ public boolean ignoreDecoding() {
+ return jacksonObj.value();
+ }
+
+ @Override
+ public boolean ignoreEncoding() {
return jacksonObj.value();
}
@@ -50,17 +74,17 @@ protected JsonProperty getJsonProperty(Annotation[] annotations) {
return new JsonProperty() {
@Override
public String value() {
- return jacksonObj.value();
+ return "";
}
@Override
public String[] from() {
- return new String[0];
+ return new String[]{jacksonObj.value()};
}
@Override
public String[] to() {
- return new String[0];
+ return new String[]{jacksonObj.value()};
}
@Override
@@ -83,6 +107,21 @@ public Class extends Encoder> encoder() {
return Encoder.class;
}
+ @Override
+ public boolean nullable() {
+ return true;
+ }
+
+ @Override
+ public boolean collectionValueNullable() {
+ return true;
+ }
+
+ @Override
+ public String defaultValueToOmit() {
+ return "";
+ }
+
@Override
public Class extends Annotation> annotationType() {
return JsonProperty.class;
@@ -108,4 +147,45 @@ public Class extends Annotation> annotationType() {
}
};
}
+
+ @Override
+ protected JsonUnwrapper getJsonUnwrapper(Annotation[] annotations) {
+ JsonUnwrapper jsoniterObj = super.getJsonUnwrapper(annotations);
+ if (jsoniterObj != null) {
+ return jsoniterObj;
+ }
+ JsonAnyGetter jacksonObj = getAnnotation(annotations, JsonAnyGetter.class);
+ if (jacksonObj == null) {
+ return null;
+ }
+ return new JsonUnwrapper() {
+ @Override
+ public Class extends Annotation> annotationType() {
+ return JsonUnwrapper.class;
+ }
+ };
+ }
+
+ @Override
+ protected JsonWrapper getJsonWrapper(Annotation[] annotations) {
+ JsonWrapper jsoniterObj = super.getJsonWrapper(annotations);
+ if (jsoniterObj != null) {
+ return jsoniterObj;
+ }
+ JsonAnySetter jacksonObj = getAnnotation(annotations, JsonAnySetter.class);
+ if (jacksonObj == null) {
+ return null;
+ }
+ return new JsonWrapper() {
+ @Override
+ public JsonWrapperType value() {
+ return JsonWrapperType.KEY_VALUE;
+ }
+
+ @Override
+ public Class extends Annotation> annotationType() {
+ return JsonWrapper.class;
+ }
+ };
+ }
}
diff --git a/src/main/java/com/jsoniter/datetime/JdkDatetimeSupport.java b/src/main/java/com/jsoniter/extra/JdkDatetimeSupport.java
similarity index 75%
rename from src/main/java/com/jsoniter/datetime/JdkDatetimeSupport.java
rename to src/main/java/com/jsoniter/extra/JdkDatetimeSupport.java
index 9b13a64a..f66c0504 100644
--- a/src/main/java/com/jsoniter/datetime/JdkDatetimeSupport.java
+++ b/src/main/java/com/jsoniter/extra/JdkDatetimeSupport.java
@@ -1,6 +1,6 @@
-package com.jsoniter.datetime;
+package com.jsoniter.extra;
-import com.jsoniter.JsonException;
+import com.jsoniter.spi.JsonException;
import com.jsoniter.JsonIterator;
import com.jsoniter.any.Any;
import com.jsoniter.output.JsonStream;
@@ -14,7 +14,7 @@
import java.util.Date;
/**
- * this is just an example
+ * there is no official way to encode/decode datetime, this is just an option for you
*/
public class JdkDatetimeSupport {
@@ -26,9 +26,12 @@ protected SimpleDateFormat initialValue() {
}
};
- public static void enable(String pattern) {
+ public static synchronized void enable(String pattern) {
+ if (JdkDatetimeSupport.pattern != null) {
+ throw new JsonException("JdkDatetimeSupport.enable can only be called once");
+ }
JdkDatetimeSupport.pattern = pattern;
- JsoniterSpi.registerTypeEncoder(Date.class, new Encoder() {
+ JsoniterSpi.registerTypeEncoder(Date.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal(sdf.get().format(obj));
diff --git a/src/main/java/com/jsoniter/extra/NamingStrategySupport.java b/src/main/java/com/jsoniter/extra/NamingStrategySupport.java
new file mode 100644
index 00000000..4a0219ff
--- /dev/null
+++ b/src/main/java/com/jsoniter/extra/NamingStrategySupport.java
@@ -0,0 +1,123 @@
+package com.jsoniter.extra;
+
+import com.jsoniter.spi.*;
+
+public class NamingStrategySupport {
+
+ public interface NamingStrategy {
+ String translate(String input);
+ }
+
+ private static boolean enabled;
+
+ public static synchronized void enable(final NamingStrategy namingStrategy) {
+ if (enabled) {
+ throw new JsonException("NamingStrategySupport.enable can only be called once");
+ }
+ enabled = true;
+ JsoniterSpi.registerExtension(new EmptyExtension() {
+ @Override
+ public void updateClassDescriptor(ClassDescriptor desc) {
+ for (Binding binding : desc.allBindings()) {
+ String translated = namingStrategy.translate(binding.name);
+ binding.toNames = new String[]{translated};
+ binding.fromNames = new String[]{translated};
+ }
+ }
+ });
+ }
+
+ public static NamingStrategy SNAKE_CASE = new NamingStrategy() {
+ @Override
+ public String translate(String input) {
+ if (input == null) return input; // garbage in, garbage out
+ int length = input.length();
+ StringBuilder result = new StringBuilder(length * 2);
+ int resultLength = 0;
+ boolean wasPrevTranslated = false;
+ for (int i = 0; i < length; i++) {
+ char c = input.charAt(i);
+ if (i > 0 || c != '_') // skip first starting underscore
+ {
+ if (Character.isUpperCase(c)) {
+ if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_') {
+ result.append('_');
+ resultLength++;
+ }
+ c = Character.toLowerCase(c);
+ wasPrevTranslated = true;
+ } else {
+ wasPrevTranslated = false;
+ }
+ result.append(c);
+ resultLength++;
+ }
+ }
+ return resultLength > 0 ? result.toString() : input;
+ }
+ };
+
+ public static NamingStrategy UPPER_CAMEL_CASE = new NamingStrategy() {
+ @Override
+ public String translate(String input) {
+ if (input == null || input.length() == 0) {
+ return input; // garbage in, garbage out
+ }
+ // Replace first lower-case letter with upper-case equivalent
+ char c = input.charAt(0);
+ char uc = Character.toUpperCase(c);
+ if (c == uc) {
+ return input;
+ }
+ StringBuilder sb = new StringBuilder(input);
+ sb.setCharAt(0, uc);
+ return sb.toString();
+ }
+ };
+
+ public static NamingStrategy LOWER_CASE = new NamingStrategy() {
+ @Override
+ public String translate(String input) {
+ return input.toLowerCase();
+ }
+ };
+
+
+ public static NamingStrategy KEBAB_CASE = new NamingStrategy() {
+ @Override
+ public String translate(String input) {
+ if (input == null) return input; // garbage in, garbage out
+ int length = input.length();
+ if (length == 0) {
+ return input;
+ }
+
+ StringBuilder result = new StringBuilder(length + (length >> 1));
+
+ int upperCount = 0;
+
+ for (int i = 0; i < length; ++i) {
+ char ch = input.charAt(i);
+ char lc = Character.toLowerCase(ch);
+
+ if (lc == ch) { // lower-case letter means we can get new word
+ // but need to check for multi-letter upper-case (acronym), where assumption
+ // is that the last upper-case char is start of a new word
+ if (upperCount > 1) {
+ // so insert hyphen before the last character now
+ result.insert(result.length() - 1, '-');
+ }
+ upperCount = 0;
+ } else {
+ // Otherwise starts new word, unless beginning of string
+ if ((upperCount == 0) && (i > 0)) {
+ result.append('-');
+ }
+ ++upperCount;
+ }
+ result.append(lc);
+ }
+ return result.toString();
+ }
+ };
+}
diff --git a/src/main/java/com/jsoniter/extra/PreciseFloatSupport.java b/src/main/java/com/jsoniter/extra/PreciseFloatSupport.java
new file mode 100644
index 00000000..8352b39b
--- /dev/null
+++ b/src/main/java/com/jsoniter/extra/PreciseFloatSupport.java
@@ -0,0 +1,60 @@
+package com.jsoniter.extra;
+
+import com.jsoniter.spi.JsonException;
+import com.jsoniter.any.Any;
+import com.jsoniter.output.JsonStream;
+import com.jsoniter.spi.Encoder;
+import com.jsoniter.spi.JsoniterSpi;
+
+import java.io.IOException;
+
+/**
+ * default float/double encoding will keep 6 decimal places
+ * enable precise encoding will use JDK toString to be precise
+ */
+public class PreciseFloatSupport {
+ private static boolean enabled;
+
+ public static synchronized void enable() {
+ if (enabled) {
+ throw new JsonException("PreciseFloatSupport.enable can only be called once");
+ }
+ enabled = true;
+ JsoniterSpi.registerTypeEncoder(Double.class, new Encoder.ReflectionEncoder() {
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ stream.writeRaw(obj.toString());
+ }
+
+ @Override
+ public Any wrap(Object obj) {
+ Double number = (Double) obj;
+ return Any.wrap(number.doubleValue());
+ }
+ });
+ JsoniterSpi.registerTypeEncoder(double.class, new Encoder.DoubleEncoder() {
+ @Override
+ public void encodeDouble(double obj, JsonStream stream) throws IOException {
+ stream.writeRaw(Double.toString(obj));
+ }
+ });
+ JsoniterSpi.registerTypeEncoder(Float.class, new Encoder.ReflectionEncoder() {
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ stream.writeRaw(obj.toString());
+ }
+
+ @Override
+ public Any wrap(Object obj) {
+ Float number = (Float) obj;
+ return Any.wrap(number.floatValue());
+ }
+ });
+ JsoniterSpi.registerTypeEncoder(float.class, new Encoder.FloatEncoder() {
+ @Override
+ public void encodeFloat(float obj, JsonStream stream) throws IOException {
+ stream.writeRaw(Float.toString(obj));
+ }
+ });
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/MaybeEmptyArrayDecoder.java b/src/main/java/com/jsoniter/fuzzy/MaybeEmptyArrayDecoder.java
new file mode 100644
index 00000000..aa4effb1
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/MaybeEmptyArrayDecoder.java
@@ -0,0 +1,31 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.JsonIterator;
+import com.jsoniter.ValueType;
+import com.jsoniter.spi.Binding;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class MaybeEmptyArrayDecoder implements Decoder {
+
+ private Binding binding;
+
+ public MaybeEmptyArrayDecoder(Binding binding) {
+ this.binding = binding;
+ }
+
+ @Override
+ public Object decode(JsonIterator iter) throws IOException {
+ if (iter.whatIsNext() == ValueType.ARRAY) {
+ if (iter.readArray()) {
+ throw iter.reportError("MaybeEmptyArrayDecoder", "this field is object. if input is array, it can only be empty");
+ } else {
+ // empty array parsed as null
+ return null;
+ }
+ } else {
+ return iter.read(binding.valueTypeLiteral);
+ }
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/MaybeStringDoubleDecoder.java b/src/main/java/com/jsoniter/fuzzy/MaybeStringDoubleDecoder.java
new file mode 100644
index 00000000..7e232543
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/MaybeStringDoubleDecoder.java
@@ -0,0 +1,25 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class MaybeStringDoubleDecoder extends Decoder.DoubleDecoder {
+
+ @Override
+ public double decodeDouble(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ CodegenAccess.unreadByte(iter);
+ return iter.readDouble();
+ }
+ double val = iter.readDouble();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringDoubleDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/MaybeStringFloatDecoder.java b/src/main/java/com/jsoniter/fuzzy/MaybeStringFloatDecoder.java
new file mode 100644
index 00000000..dd8ed6e6
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/MaybeStringFloatDecoder.java
@@ -0,0 +1,25 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class MaybeStringFloatDecoder extends Decoder.FloatDecoder {
+
+ @Override
+ public float decodeFloat(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ CodegenAccess.unreadByte(iter);
+ return iter.readFloat();
+ }
+ float val = iter.readFloat();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringFloatDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/MaybeStringIntDecoder.java b/src/main/java/com/jsoniter/fuzzy/MaybeStringIntDecoder.java
new file mode 100644
index 00000000..3e02351f
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/MaybeStringIntDecoder.java
@@ -0,0 +1,25 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class MaybeStringIntDecoder extends Decoder.IntDecoder {
+
+ @Override
+ public int decodeInt(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ CodegenAccess.unreadByte(iter);
+ return iter.readInt();
+ }
+ int val = iter.readInt();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringIntDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/MaybeStringLongDecoder.java b/src/main/java/com/jsoniter/fuzzy/MaybeStringLongDecoder.java
new file mode 100644
index 00000000..53709e1d
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/MaybeStringLongDecoder.java
@@ -0,0 +1,25 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class MaybeStringLongDecoder extends Decoder.LongDecoder {
+
+ @Override
+ public long decodeLong(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ CodegenAccess.unreadByte(iter);
+ return iter.readLong();
+ }
+ long val = iter.readLong();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringLongDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/MaybeStringShortDecoder.java b/src/main/java/com/jsoniter/fuzzy/MaybeStringShortDecoder.java
new file mode 100644
index 00000000..075646dc
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/MaybeStringShortDecoder.java
@@ -0,0 +1,25 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class MaybeStringShortDecoder extends Decoder.ShortDecoder {
+
+ @Override
+ public short decodeShort(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ CodegenAccess.unreadByte(iter);
+ return iter.readShort();
+ }
+ short val = iter.readShort();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringShortDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/StringDoubleDecoder.java b/src/main/java/com/jsoniter/fuzzy/StringDoubleDecoder.java
new file mode 100644
index 00000000..f419727f
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/StringDoubleDecoder.java
@@ -0,0 +1,24 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class StringDoubleDecoder extends Decoder.DoubleDecoder {
+
+ @Override
+ public double decodeDouble(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringDoubleDecoder", "expect \", but found: " + (char) c);
+ }
+ double val = iter.readDouble();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringDoubleDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/StringFloatDecoder.java b/src/main/java/com/jsoniter/fuzzy/StringFloatDecoder.java
new file mode 100644
index 00000000..e4b5f0d7
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/StringFloatDecoder.java
@@ -0,0 +1,24 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class StringFloatDecoder extends Decoder.FloatDecoder {
+
+ @Override
+ public float decodeFloat(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringFloatDecoder", "expect \", but found: " + (char) c);
+ }
+ float val = iter.readFloat();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringFloatDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/StringIntDecoder.java b/src/main/java/com/jsoniter/fuzzy/StringIntDecoder.java
new file mode 100644
index 00000000..5bd6096a
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/StringIntDecoder.java
@@ -0,0 +1,24 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class StringIntDecoder extends Decoder.IntDecoder {
+
+ @Override
+ public int decodeInt(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringIntDecoder", "expect \", but found: " + (char) c);
+ }
+ int val = iter.readInt();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringIntDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/StringLongDecoder.java b/src/main/java/com/jsoniter/fuzzy/StringLongDecoder.java
new file mode 100644
index 00000000..246d9399
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/StringLongDecoder.java
@@ -0,0 +1,24 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class StringLongDecoder extends Decoder.LongDecoder {
+
+ @Override
+ public long decodeLong(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringLongDecoder", "expect \", but found: " + (char) c);
+ }
+ long val = iter.readLong();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringLongDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/fuzzy/StringShortDecoder.java b/src/main/java/com/jsoniter/fuzzy/StringShortDecoder.java
new file mode 100644
index 00000000..5d10298e
--- /dev/null
+++ b/src/main/java/com/jsoniter/fuzzy/StringShortDecoder.java
@@ -0,0 +1,24 @@
+package com.jsoniter.fuzzy;
+
+import com.jsoniter.CodegenAccess;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.spi.Decoder;
+
+import java.io.IOException;
+
+public class StringShortDecoder extends Decoder.ShortDecoder {
+
+ @Override
+ public short decodeShort(JsonIterator iter) throws IOException {
+ byte c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringShortDecoder", "expect \", but found: " + (char) c);
+ }
+ short val = iter.readShort();
+ c = CodegenAccess.nextToken(iter);
+ if (c != '"') {
+ throw iter.reportError("StringShortDecoder", "expect \", but found: " + (char) c);
+ }
+ return val;
+ }
+}
diff --git a/src/main/java/com/jsoniter/output/AsciiOutputStream.java b/src/main/java/com/jsoniter/output/AsciiOutputStream.java
deleted file mode 100644
index a2fc78ce..00000000
--- a/src/main/java/com/jsoniter/output/AsciiOutputStream.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.jsoniter.output;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-class AsciiOutputStream extends OutputStream {
- private char[] buf = new char[4096];
- private int count = 0;
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- int i = off;
- for (; ; ) {
- for (; i < off + len && count < buf.length; i++) {
- buf[count++] = (char) b[i];
- }
- if (count == buf.length) {
- char[] newBuf = new char[buf.length * 2];
- System.arraycopy(buf, 0, newBuf, 0, buf.length);
- buf = newBuf;
- } else {
- break;
- }
- }
- }
-
- @Override
- public void write(int b) throws IOException {
- if (count == buf.length) {
- char[] newBuf = new char[buf.length * 2];
- System.arraycopy(buf, 0, newBuf, 0, buf.length);
- buf = newBuf;
- }
- buf[count++] = (char) b;
- }
-
- @Override
- public String toString() {
- return new String(buf, 0, count);
- }
-
- public void reset() {
- count = 0;
- }
-}
diff --git a/src/main/java/com/jsoniter/output/Codegen.java b/src/main/java/com/jsoniter/output/Codegen.java
index ff118694..7680244d 100644
--- a/src/main/java/com/jsoniter/output/Codegen.java
+++ b/src/main/java/com/jsoniter/output/Codegen.java
@@ -1,41 +1,25 @@
package com.jsoniter.output;
-import com.jsoniter.JsonException;
-import com.jsoniter.spi.Encoder;
-import com.jsoniter.spi.Extension;
-import com.jsoniter.spi.JsoniterSpi;
-import com.jsoniter.spi.TypeLiteral;
+import com.jsoniter.spi.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
+import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
class Codegen {
- private static EncodingMode mode = EncodingMode.REFLECTION_MODE;
- static boolean isDoingStaticCodegen;
+ static CodegenAccess.StaticCodegenTarget isDoingStaticCodegen;
// only read/write when generating code with synchronized protection
- private final static Set generatedClassNames = new HashSet();
- private volatile static Map reflectionEncoders = new HashMap();
+ private final static Map generatedSources = new HashMap();
+ private volatile static Map reflectionEncoders = new HashMap();
- static {
- String envMode = System.getenv("JSONITER_ENCODING_MODE");
- if (envMode != null) {
- mode = EncodingMode.valueOf(envMode);
- }
- }
-
- public static void setMode(EncodingMode mode) {
- Codegen.mode = mode;
- }
-
-
- public static Encoder getReflectionEncoder(String cacheKey, Type type) {
- Encoder encoder = CodegenImplNative.NATIVE_ENCODERS.get(type);
+ public static Encoder.ReflectionEncoder getReflectionEncoder(String cacheKey, Type type) {
+ Encoder.ReflectionEncoder encoder = CodegenImplNative.NATIVE_ENCODERS.get(type);
if (encoder != null) {
return encoder;
}
@@ -48,17 +32,9 @@ public static Encoder getReflectionEncoder(String cacheKey, Type type) {
if (encoder != null) {
return encoder;
}
- Type[] typeArgs = new Type[0];
- Class clazz;
- if (type instanceof ParameterizedType) {
- ParameterizedType pType = (ParameterizedType) type;
- clazz = (Class) pType.getRawType();
- typeArgs = pType.getActualTypeArguments();
- } else {
- clazz = (Class) type;
- }
- encoder = ReflectionEncoderFactory.create(clazz, typeArgs);
- HashMap copy = new HashMap(reflectionEncoders);
+ ClassInfo classInfo = new ClassInfo(type);
+ encoder = ReflectionEncoderFactory.create(classInfo);
+ HashMap copy = new HashMap(reflectionEncoders);
copy.put(cacheKey, encoder);
reflectionEncoders = copy;
return encoder;
@@ -73,7 +49,7 @@ public static Encoder getEncoder(String cacheKey, Type type) {
return gen(cacheKey, type);
}
- private static synchronized Encoder gen(String cacheKey, Type type) {
+ private static synchronized Encoder gen(final String cacheKey, Type type) {
Encoder encoder = JsoniterSpi.getEncoder(cacheKey);
if (encoder != null) {
return encoder;
@@ -91,6 +67,81 @@ private static synchronized Encoder gen(String cacheKey, Type type) {
JsoniterSpi.addNewEncoder(cacheKey, encoder);
return encoder;
}
+ addPlaceholderEncoderToSupportRecursiveStructure(cacheKey);
+ try {
+ EncodingMode mode = JsoniterSpi.getCurrentConfig().encodingMode();
+ if (mode != EncodingMode.REFLECTION_MODE) {
+ Type originalType = type;
+ type = chooseAccessibleSuper(type);
+ if (Object.class == type) {
+ throw new JsonException("dynamic code can not serialize private class: " + originalType);
+ }
+ }
+ ClassInfo classInfo = new ClassInfo(type);
+ if (Map.class.isAssignableFrom(classInfo.clazz) && classInfo.typeArgs.length > 1) {
+ MapKeyEncoders.registerOrGetExisting(classInfo.typeArgs[0]);
+ }
+ if (mode == EncodingMode.REFLECTION_MODE) {
+ encoder = ReflectionEncoderFactory.create(classInfo);
+ return encoder;
+ }
+ if (isDoingStaticCodegen == null) {
+ try {
+ encoder = (Encoder) Class.forName(cacheKey).newInstance();
+ return encoder;
+ } catch (Exception e) {
+ if (mode == EncodingMode.STATIC_MODE) {
+ throw new JsonException("static gen should provide the encoder we need, but failed to create the encoder", e);
+ }
+ }
+ }
+ CodegenResult source = genSource(cacheKey, classInfo);
+ try {
+ generatedSources.put(cacheKey, source);
+ if (isDoingStaticCodegen == null) {
+ encoder = DynamicCodegen.gen(classInfo.clazz, cacheKey, source);
+ } else {
+ staticGen(classInfo.clazz, cacheKey, source);
+ }
+ return encoder;
+ } catch (Exception e) {
+ String msg = "failed to generate encoder for: " + type + " with " + Arrays.toString(classInfo.typeArgs) + ", exception: " + e;
+ msg = msg + "\n" + source;
+ throw new JsonException(msg, e);
+ }
+ } finally {
+ JsoniterSpi.addNewEncoder(cacheKey, encoder);
+ }
+ }
+
+ private static void addPlaceholderEncoderToSupportRecursiveStructure(final String cacheKey) {
+ JsoniterSpi.addNewEncoder(cacheKey, new Encoder() {
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ Encoder encoder = JsoniterSpi.getEncoder(cacheKey);
+ if (this == encoder) {
+ for(int i = 0; i < 30; i++) {
+ encoder = JsoniterSpi.getEncoder(cacheKey);
+ if (this == encoder) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ throw new JsonException(e);
+ }
+ } else {
+ break;
+ }
+ }
+ if (this == encoder) {
+ throw new JsonException("internal error: placeholder is not replaced with real encoder");
+ }
+ }
+ encoder.encode(obj, stream);
+ }
+ });
+ }
+
+ private static Type chooseAccessibleSuper(Type type) {
Type[] typeArgs = new Type[0];
Class clazz;
if (type instanceof ParameterizedType) {
@@ -100,50 +151,32 @@ private static synchronized Encoder gen(String cacheKey, Type type) {
} else {
clazz = (Class) type;
}
- if (mode == EncodingMode.REFLECTION_MODE) {
- encoder = ReflectionEncoderFactory.create(clazz, typeArgs);
- JsoniterSpi.addNewEncoder(cacheKey, encoder);
- return encoder;
+ if (Modifier.isPublic(clazz.getModifiers())) {
+ return type;
}
- try {
- encoder = (Encoder) Class.forName(cacheKey).newInstance();
- JsoniterSpi.addNewEncoder(cacheKey, encoder);
- return encoder;
- } catch (Exception e) {
- if (mode == EncodingMode.STATIC_MODE) {
- throw new JsonException("static gen should provide the encoder we need, but failed to create the encoder", e);
- }
- }
- String source = genSource(clazz, typeArgs);
- if ("true".equals(System.getenv("JSONITER_DEBUG"))) {
- System.out.println(">>> " + cacheKey);
- System.out.println(source);
+ clazz = walkSuperUntilPublic(clazz.getSuperclass());
+ if (typeArgs.length == 0) {
+ return clazz;
+ } else {
+ return GenericsHelper.createParameterizedType(typeArgs, null, clazz);
}
- try {
- generatedClassNames.add(cacheKey);
- if (isDoingStaticCodegen) {
- staticGen(clazz, cacheKey, source);
- } else {
- encoder = DynamicCodegen.gen(clazz, cacheKey, source);
- }
- JsoniterSpi.addNewEncoder(cacheKey, encoder);
- return encoder;
- } catch (Exception e) {
- System.err.println("failed to generate encoder for: " + type + " with " + Arrays.toString(typeArgs) + ", exception: " + e);
- System.err.println(source);
- JsoniterSpi.dump();
- throw new JsonException(e);
+ }
+
+ private static Class walkSuperUntilPublic(Class clazz) {
+ if (Modifier.isPublic(clazz.getModifiers())) {
+ return clazz;
}
+ return walkSuperUntilPublic(clazz.getSuperclass());
}
- public static boolean canStaticAccess(String cacheKey) {
- return generatedClassNames.contains(cacheKey);
+ public static CodegenResult getGeneratedSource(String cacheKey) {
+ return generatedSources.get(cacheKey);
}
- private static void staticGen(Class clazz, String cacheKey, String source) throws IOException {
+ private static void staticGen(Class clazz, String cacheKey, CodegenResult source) throws IOException {
createDir(cacheKey);
String fileName = cacheKey.replace('.', '/') + ".java";
- FileOutputStream fileOutputStream = new FileOutputStream(fileName);
+ FileOutputStream fileOutputStream = new FileOutputStream(new File(isDoingStaticCodegen.outputDir, fileName));
try {
OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream);
try {
@@ -156,21 +189,19 @@ private static void staticGen(Class clazz, String cacheKey, String source) throw
}
}
- private static void staticGen(Class clazz, String cacheKey, OutputStreamWriter writer, String source) throws IOException {
+ private static void staticGen(Class clazz, String cacheKey, OutputStreamWriter writer, CodegenResult source) throws IOException {
String className = cacheKey.substring(cacheKey.lastIndexOf('.') + 1);
String packageName = cacheKey.substring(0, cacheKey.lastIndexOf('.'));
writer.write("package " + packageName + ";\n");
- writer.write("public class " + className + " extends com.jsoniter.spi.EmptyEncoder {\n");
- writer.write(source);
- writer.write("public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {\n");
- writer.write(String.format("encode_((%s)obj, stream);\n", clazz.getCanonicalName()));
- writer.write("}\n");
+ writer.write("public class " + className + " implements com.jsoniter.spi.Encoder {\n");
+ writer.write(source.generateWrapperCode(clazz));
+ writer.write(source.toString());
writer.write("}\n");
}
private static void createDir(String cacheKey) {
String[] parts = cacheKey.split("\\.");
- File parent = new File(".");
+ File parent = new File(isDoingStaticCodegen.outputDir);
for (int i = 0; i < parts.length - 1; i++) {
String part = parts[i];
File current = new File(parent, part);
@@ -179,24 +210,25 @@ private static void createDir(String cacheKey) {
}
}
- private static String genSource(Class clazz, Type[] typeArgs) {
+ private static CodegenResult genSource(String cacheKey, ClassInfo classInfo) {
+ Class clazz = classInfo.clazz;
if (clazz.isArray()) {
- return CodegenImplArray.genArray(clazz);
+ return CodegenImplArray.genArray(cacheKey, classInfo);
}
if (Map.class.isAssignableFrom(clazz)) {
- return CodegenImplMap.genMap(clazz, typeArgs);
+ return CodegenImplMap.genMap(cacheKey, classInfo);
}
if (Collection.class.isAssignableFrom(clazz)) {
- return CodegenImplArray.genCollection(clazz, typeArgs);
+ return CodegenImplArray.genCollection(cacheKey, classInfo);
}
if (clazz.isEnum()) {
return CodegenImplNative.genEnum(clazz);
}
- return CodegenImplObject.genObject(clazz);
+ return CodegenImplObject.genObject(classInfo);
}
- public static void staticGenEncoders(TypeLiteral[] typeLiterals) {
- isDoingStaticCodegen = true;
+ public static void staticGenEncoders(TypeLiteral[] typeLiterals, CodegenAccess.StaticCodegenTarget staticCodegenTarget) {
+ isDoingStaticCodegen = staticCodegenTarget;
for (TypeLiteral typeLiteral : typeLiterals) {
gen(typeLiteral.getEncoderCacheKey(), typeLiteral.getType());
}
diff --git a/src/main/java/com/jsoniter/output/CodegenAccess.java b/src/main/java/com/jsoniter/output/CodegenAccess.java
index 0bf53c59..913df649 100644
--- a/src/main/java/com/jsoniter/output/CodegenAccess.java
+++ b/src/main/java/com/jsoniter/output/CodegenAccess.java
@@ -1,6 +1,6 @@
package com.jsoniter.output;
-import com.jsoniter.*;
+import com.jsoniter.any.Any;
import com.jsoniter.spi.Encoder;
import com.jsoniter.spi.JsoniterSpi;
import com.jsoniter.spi.TypeLiteral;
@@ -52,11 +52,34 @@ public static void writeVal(String cacheKey, double obj, JsonStream stream) thro
encoder.encodeDouble(obj, stream);
}
- public static void writeStringWithoutQuote(JsonStream stream, String val) throws IOException {
- StreamImplString.writeString(stream, val);
+ public static void writeMapKey(String cacheKey, Object mapKey, JsonStream stream) throws IOException {
+ Encoder mapKeyEncoder = JsoniterSpi.getMapKeyEncoder(cacheKey);
+ mapKeyEncoder.encode(mapKey, stream);
}
- public static void staticGenEncoders(TypeLiteral[] typeLiterals) {
- Codegen.staticGenEncoders(typeLiterals);
+ public static void writeStringWithoutQuote(String obj, JsonStream stream) throws IOException {
+ StreamImplString.writeStringWithoutQuote(stream, obj);
+ }
+
+ public static void staticGenEncoders(TypeLiteral[] typeLiterals, StaticCodegenTarget staticCodegenTarget) {
+ Codegen.staticGenEncoders(typeLiterals, staticCodegenTarget);
+ }
+
+ public static Any wrap(Object val) {
+ if (val == null) {
+ return Any.wrapNull();
+ }
+ Class> clazz = val.getClass();
+ String cacheKey = TypeLiteral.create(clazz).getEncoderCacheKey();
+ return Codegen.getReflectionEncoder(cacheKey, clazz).wrap(val);
+ }
+
+ public static class StaticCodegenTarget {
+
+ public final String outputDir;
+
+ public StaticCodegenTarget(String outputDir) {
+ this.outputDir = outputDir;
+ }
}
}
diff --git a/src/main/java/com/jsoniter/output/CodegenContext.java b/src/main/java/com/jsoniter/output/CodegenContext.java
deleted file mode 100644
index f89eb80c..00000000
--- a/src/main/java/com/jsoniter/output/CodegenContext.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.jsoniter.output;
-
-class CodegenContext {
-
- StringBuilder lines = new StringBuilder();
- StringBuilder buffered = new StringBuilder();
-
- public void append(String str) {
- lines.append(str);
- lines.append("\n");
- }
-
- public void flushBuffer() {
- if (buffered.length() == 0) {
- return;
- }
- if (buffered.length() == 1) {
- append(String.format("stream.write('%s');", buffered.toString()));
- } else {
- append(String.format("stream.writeRaw(\"%s\");", buffered.toString()));
- }
- buffered.setLength(0);
- }
-
- public String toString() {
- return lines.toString();
- }
-}
diff --git a/src/main/java/com/jsoniter/output/CodegenImplArray.java b/src/main/java/com/jsoniter/output/CodegenImplArray.java
index bc12bd75..19af5531 100644
--- a/src/main/java/com/jsoniter/output/CodegenImplArray.java
+++ b/src/main/java/com/jsoniter/output/CodegenImplArray.java
@@ -1,40 +1,16 @@
package com.jsoniter.output;
+import com.jsoniter.spi.ClassInfo;
+import com.jsoniter.spi.JsoniterSpi;
+
import java.lang.reflect.Type;
import java.util.*;
class CodegenImplArray {
- public static String genArray(Class clazz) {
- Class compType = clazz.getComponentType();
- if (compType.isArray()) {
- throw new IllegalArgumentException("nested array not supported: " + clazz.getCanonicalName());
- }
- StringBuilder lines = new StringBuilder();
- append(lines, "public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {");
- append(lines, "if (obj == null) { stream.writeNull(); return; }");
- append(lines, "{{comp}}[] arr = ({{comp}}[])obj;");
- append(lines, "if (arr.length == 0) { stream.writeEmptyArray(); return; }");
- append(lines, "stream.writeArrayStart();");
- append(lines, "int i = 0;");
- append(lines, "{{op}}");
- append(lines, "while (i < arr.length) {");
- append(lines, "stream.writeMore();");
- append(lines, "{{op}}");
- append(lines, "}");
- append(lines, "stream.writeArrayEnd();");
- append(lines, "}");
- return lines.toString()
- .replace("{{comp}}", compType.getCanonicalName())
- .replace("{{op}}", CodegenImplNative.genWriteOp("arr[i++]", compType));
- }
-
- private static void append(StringBuilder lines, String str) {
- lines.append(str);
- lines.append("\n");
- }
-
- public static String genCollection(Class clazz, Type[] typeArgs) {
+ public static CodegenResult genCollection(String cacheKey, ClassInfo classInfo) {
+ Type[] typeArgs = classInfo.typeArgs;
+ Class clazz = classInfo.clazz;
Type compType = Object.class;
if (typeArgs.length == 0) {
// default to List
@@ -50,26 +26,165 @@ public static String genCollection(Class clazz, Type[] typeArgs) {
} else if (clazz == Set.class) {
clazz = HashSet.class;
}
- return genCollection(clazz, compType);
+ if (List.class.isAssignableFrom(clazz)) {
+ return genList(cacheKey, clazz, compType);
+ } else {
+ return genCollection(cacheKey, clazz, compType);
+ }
}
- private static String genCollection(Class clazz, Type compType) {
- StringBuilder lines = new StringBuilder();
- append(lines, "public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {");
- append(lines, "if (obj == null) { stream.writeNull(); return; }");
- append(lines, "java.util.Iterator iter = ((java.util.Collection)obj).iterator();");
- append(lines, "if (!iter.hasNext()) { stream.writeEmptyArray(); return; }");
- append(lines, "stream.writeArrayStart();");
- append(lines, "{{op}}");
- append(lines, "while (iter.hasNext()) {");
- append(lines, "stream.writeMore();");
- append(lines, "{{op}}");
- append(lines, "}");
- append(lines, "stream.writeArrayEnd();");
- append(lines, "}");
- return lines.toString()
- .replace("{{comp}}", CodegenImplNative.getTypeName(compType))
- .replace("{{op}}", CodegenImplNative.genWriteOp("iter.next()", compType));
+ public static CodegenResult genArray(String cacheKey, ClassInfo classInfo) {
+ boolean noIndention = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
+ Class clazz = classInfo.clazz;
+ Class compType = clazz.getComponentType();
+ if (compType.isArray()) {
+ throw new IllegalArgumentException("nested array not supported: " + clazz.getCanonicalName());
+ }
+ boolean isCollectionValueNullable = true;
+ if (cacheKey.endsWith("__value_not_nullable")) {
+ isCollectionValueNullable = false;
+ }
+ if (compType.isPrimitive()) {
+ isCollectionValueNullable = false;
+ }
+ CodegenResult ctx = new CodegenResult();
+ ctx.append("public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {");
+ ctx.append(String.format("%s[] arr = (%s[])obj;", compType.getCanonicalName(), compType.getCanonicalName()));
+ if (noIndention) {
+ ctx.append("if (arr.length == 0) { return; }");
+ ctx.buffer('[');
+ } else {
+ ctx.append("if (arr.length == 0) { stream.write((byte)'[', (byte)']'); return; }");
+ ctx.append("stream.writeArrayStart(); stream.writeIndention();");
+ }
+ ctx.append("int i = 0;");
+ ctx.append(String.format("%s e = arr[i++];", compType.getCanonicalName()));
+ if (isCollectionValueNullable) {
+ ctx.append("if (e == null) { stream.writeNull(); } else {");
+ CodegenImplNative.genWriteOp(ctx, "e", compType, true);
+ ctx.append("}"); // if
+ } else {
+ CodegenImplNative.genWriteOp(ctx, "e", compType, false);
+ }
+ ctx.append("while (i < arr.length) {");
+ if (noIndention) {
+ ctx.append("stream.write(',');");
+ } else {
+ ctx.append("stream.writeMore();");
+ }
+ ctx.append("e = arr[i++];");
+ if (isCollectionValueNullable) {
+ ctx.append("if (e == null) { stream.writeNull(); } else {");
+ CodegenImplNative.genWriteOp(ctx, "e", compType, true);
+ ctx.append("}"); // if
+ } else {
+ CodegenImplNative.genWriteOp(ctx, "e", compType, false);
+ }
+ ctx.append("}"); // while
+ if (noIndention) {
+ ctx.buffer(']');
+ } else {
+ ctx.append("stream.writeArrayEnd();");
+ }
+ ctx.append("}"); // public static void encode_
+ return ctx;
+ }
+
+ private static CodegenResult genList(String cacheKey, Class clazz, Type compType) {
+ boolean noIndention = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
+ boolean isCollectionValueNullable = true;
+ if (cacheKey.endsWith("__value_not_nullable")) {
+ isCollectionValueNullable = false;
+ }
+ CodegenResult ctx = new CodegenResult();
+ ctx.append("public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {");
+ ctx.append("java.util.List list = (java.util.List)obj;");
+ ctx.append("int size = list.size();");
+ if (noIndention) {
+ ctx.append("if (size == 0) { return; }");
+ ctx.buffer('[');
+ } else {
+ ctx.append("if (size == 0) { stream.write((byte)'[', (byte)']'); return; }");
+ ctx.append("stream.writeArrayStart(); stream.writeIndention();");
+ }
+ ctx.append("java.lang.Object e = list.get(0);");
+ if (isCollectionValueNullable) {
+ ctx.append("if (e == null) { stream.writeNull(); } else {");
+ CodegenImplNative.genWriteOp(ctx, "e", compType, true);
+ ctx.append("}");
+ } else {
+ CodegenImplNative.genWriteOp(ctx, "e", compType, false);
+ }
+ ctx.append("for (int i = 1; i < size; i++) {");
+ if (noIndention) {
+ ctx.append("stream.write(',');");
+ } else {
+ ctx.append("stream.writeMore();");
+ }
+ ctx.append("e = list.get(i);");
+ if (isCollectionValueNullable) {
+ ctx.append("if (e == null) { stream.writeNull(); } else {");
+ CodegenImplNative.genWriteOp(ctx, "e", compType, true);
+ ctx.append("}"); // if
+ } else {
+ CodegenImplNative.genWriteOp(ctx, "e", compType, false);
+ }
+ ctx.append("}"); // for
+ if (noIndention) {
+ ctx.buffer(']');
+ } else {
+ ctx.append("stream.writeArrayEnd();");
+ }
+ ctx.append("}"); // public static void encode_
+ return ctx;
+ }
+
+ private static CodegenResult genCollection(String cacheKey, Class clazz, Type compType) {
+ boolean noIndention = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
+ boolean isCollectionValueNullable = true;
+ if (cacheKey.endsWith("__value_not_nullable")) {
+ isCollectionValueNullable = false;
+ }
+ CodegenResult ctx = new CodegenResult();
+ ctx.append("public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {");
+ ctx.append("java.util.Iterator iter = ((java.util.Collection)obj).iterator();");
+ if (noIndention) {
+ ctx.append("if (!iter.hasNext()) { return; }");
+ ctx.buffer('[');
+ } else {
+ ctx.append("if (!iter.hasNext()) { stream.write((byte)'[', (byte)']'); return; }");
+ ctx.append("stream.writeArrayStart(); stream.writeIndention();");
+ }
+ ctx.append("java.lang.Object e = iter.next();");
+ if (isCollectionValueNullable) {
+ ctx.append("if (e == null) { stream.writeNull(); } else {");
+ CodegenImplNative.genWriteOp(ctx, "e", compType, true);
+ ctx.append("}"); // if
+ } else {
+ CodegenImplNative.genWriteOp(ctx, "e", compType, false);
+ }
+ ctx.append("while (iter.hasNext()) {");
+ if (noIndention) {
+ ctx.append("stream.write(',');");
+ } else {
+ ctx.append("stream.writeMore();");
+ }
+ ctx.append("e = iter.next();");
+ if (isCollectionValueNullable) {
+ ctx.append("if (e == null) { stream.writeNull(); } else {");
+ CodegenImplNative.genWriteOp(ctx, "e", compType, true);
+ ctx.append("}"); // if
+ } else {
+ CodegenImplNative.genWriteOp(ctx, "e", compType, false);
+ }
+ ctx.append("}"); // while
+ if (noIndention) {
+ ctx.buffer(']');
+ } else {
+ ctx.append("stream.writeArrayEnd();");
+ }
+ ctx.append("}"); // public static void encode_
+ return ctx;
}
}
diff --git a/src/main/java/com/jsoniter/output/CodegenImplMap.java b/src/main/java/com/jsoniter/output/CodegenImplMap.java
index 093ef75b..006817d3 100644
--- a/src/main/java/com/jsoniter/output/CodegenImplMap.java
+++ b/src/main/java/com/jsoniter/output/CodegenImplMap.java
@@ -1,54 +1,92 @@
package com.jsoniter.output;
+import com.jsoniter.spi.ClassInfo;
+import com.jsoniter.spi.JsoniterSpi;
+
import java.lang.reflect.Type;
-import java.util.HashMap;
-import java.util.Map;
class CodegenImplMap {
- public static String genMap(Class clazz, Type[] typeArgs) {
- Type keyType = String.class;
+ public static CodegenResult genMap(String cacheKey, ClassInfo classInfo) {
+ boolean noIndention = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
+ Type[] typeArgs = classInfo.typeArgs;
+ boolean isCollectionValueNullable = true;
+ if (cacheKey.endsWith("__value_not_nullable")) {
+ isCollectionValueNullable = false;
+ }
+ Type keyType = Object.class;
Type valueType = Object.class;
- if (typeArgs.length == 0) {
- // default to Map
- } else if (typeArgs.length == 2) {
+ if (typeArgs.length == 2) {
keyType = typeArgs[0];
valueType = typeArgs[1];
+ }
+ CodegenResult ctx = new CodegenResult();
+ ctx.append("public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {");
+ ctx.append("if (obj == null) { stream.writeNull(); return; }");
+ ctx.append("java.util.Map map = (java.util.Map)obj;");
+ ctx.append("java.util.Iterator iter = map.entrySet().iterator();");
+ if (noIndention) {
+ ctx.append("if(!iter.hasNext()) { return; }");
+ } else {
+ ctx.append("if(!iter.hasNext()) { stream.write((byte)'{', (byte)'}'); return; }");
+ }
+ ctx.append("java.util.Map.Entry entry = (java.util.Map.Entry)iter.next();");
+ if (noIndention) {
+ ctx.buffer('{');
} else {
- throw new IllegalArgumentException(
- "can not bind to generic collection without argument types, " +
- "try syntax like TypeLiteral>{}");
- }
- if (keyType != String.class) {
- throw new IllegalArgumentException("map key must be String");
- }
- if (clazz == Map.class) {
- clazz = HashMap.class;
- }
- StringBuilder lines = new StringBuilder();
- append(lines, "public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {");
- append(lines, "if (obj == null) { stream.writeNull(); return; }");
- append(lines, "java.util.Map map = (java.util.Map)obj;");
- append(lines, "java.util.Iterator iter = map.entrySet().iterator();");
- append(lines, "if(!iter.hasNext()) { stream.writeEmptyObject(); return; }");
- append(lines, "java.util.Map.Entry entry = (java.util.Map.Entry)iter.next();");
- append(lines, "stream.writeObjectStart();");
- append(lines, "stream.writeObjectField((String)entry.getKey());");
- append(lines, "{{op}}");
- append(lines, "while(iter.hasNext()) {");
- append(lines, "entry = (java.util.Map.Entry)iter.next();");
- append(lines, "stream.writeMore();");
- append(lines, "stream.writeObjectField((String)entry.getKey());");
- append(lines, "{{op}}");
- append(lines, "}");
- append(lines, "stream.writeObjectEnd();");
- append(lines, "}");
- return lines.toString()
- .replace("{{clazz}}", clazz.getName())
- .replace("{{op}}", CodegenImplNative.genWriteOp("entry.getValue()", valueType));
+ ctx.append("stream.writeObjectStart(); stream.writeIndention();");
+ }
+ genWriteMapKey(ctx, keyType, noIndention);
+ if (isCollectionValueNullable) {
+ ctx.append("if (entry.getValue() == null) { stream.writeNull(); } else {");
+ CodegenImplNative.genWriteOp(ctx, "entry.getValue()", valueType, true);
+ ctx.append("}");
+ } else {
+ CodegenImplNative.genWriteOp(ctx, "entry.getValue()", valueType, false);
+ }
+ ctx.append("while(iter.hasNext()) {");
+ ctx.append("entry = (java.util.Map.Entry)iter.next();");
+ if (noIndention) {
+ ctx.append("stream.write(',');");
+ } else {
+ ctx.append("stream.writeMore();");
+ }
+ genWriteMapKey(ctx, keyType, noIndention);
+ if (isCollectionValueNullable) {
+ ctx.append("if (entry.getValue() == null) { stream.writeNull(); } else {");
+ CodegenImplNative.genWriteOp(ctx, "entry.getValue()", valueType, true);
+ ctx.append("}");
+ } else {
+ CodegenImplNative.genWriteOp(ctx, "entry.getValue()", valueType, false);
+ }
+ ctx.append("}");
+ if (noIndention) {
+ ctx.buffer('}');
+ } else {
+ ctx.append("stream.writeObjectEnd();");
+ }
+ ctx.append("}");
+ return ctx;
}
- private static void append(StringBuilder lines, String str) {
- lines.append(str);
- lines.append("\n");
+ private static void genWriteMapKey(CodegenResult ctx, Type keyType, boolean noIndention) {
+ if (keyType == Object.class) {
+ ctx.append("stream.writeObjectField(entry.getKey());");
+ return;
+ }
+ if (keyType == String.class) {
+ ctx.append("stream.writeVal((java.lang.String)entry.getKey());");
+ } else if (CodegenImplNative.NATIVE_ENCODERS.containsKey(keyType)) {
+ ctx.append("stream.write('\"');");
+ ctx.append(String.format("stream.writeVal((%s)entry.getKey());", CodegenImplNative.getTypeName(keyType)));
+ ctx.append("stream.write('\"');");
+ } else {
+ String mapCacheKey = JsoniterSpi.getMapKeyEncoderCacheKey(keyType);
+ ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeMapKey(\"%s\", entry.getKey(), stream);", mapCacheKey));
+ }
+ if (noIndention) {
+ ctx.append("stream.write(':');");
+ } else {
+ ctx.append("stream.write((byte)':', (byte)' ');");
+ }
}
}
diff --git a/src/main/java/com/jsoniter/output/CodegenImplNative.java b/src/main/java/com/jsoniter/output/CodegenImplNative.java
index e92f46bb..4237a5d2 100644
--- a/src/main/java/com/jsoniter/output/CodegenImplNative.java
+++ b/src/main/java/com/jsoniter/output/CodegenImplNative.java
@@ -1,13 +1,13 @@
package com.jsoniter.output;
-import com.jsoniter.JsonException;
+import com.jsoniter.spi.JsonException;
import com.jsoniter.any.Any;
import com.jsoniter.spi.*;
import java.io.IOException;
-import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
@@ -15,8 +15,8 @@
import java.util.Map;
class CodegenImplNative {
- public static final Map NATIVE_ENCODERS = new IdentityHashMap() {{
- put(boolean.class, new Encoder() {
+ public static final Map NATIVE_ENCODERS = new IdentityHashMap() {{
+ put(boolean.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Boolean) obj);
@@ -28,7 +28,7 @@ public Any wrap(Object obj) {
return Any.wrap((boolean) val);
}
});
- put(Boolean.class, new Encoder() {
+ put(Boolean.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Boolean) obj);
@@ -40,10 +40,10 @@ public Any wrap(Object obj) {
return Any.wrap((boolean) val);
}
});
- put(byte.class, new Encoder() {
+ put(byte.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
- stream.writeVal((Short) obj);
+ stream.writeVal(((Byte) obj).shortValue());
}
@Override
@@ -52,10 +52,10 @@ public Any wrap(Object obj) {
return Any.wrap((int) val);
}
});
- put(Byte.class, new Encoder() {
+ put(Byte.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
- stream.writeVal((Short) obj);
+ stream.writeVal(((Byte) obj).shortValue());
}
@Override
@@ -64,7 +64,7 @@ public Any wrap(Object obj) {
return Any.wrap((int) val);
}
});
- put(short.class, new Encoder() {
+ put(short.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Short) obj);
@@ -76,7 +76,7 @@ public Any wrap(Object obj) {
return Any.wrap((int) val);
}
});
- put(Short.class, new Encoder() {
+ put(Short.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Short) obj);
@@ -88,7 +88,7 @@ public Any wrap(Object obj) {
return Any.wrap((int) val);
}
});
- put(int.class, new Encoder() {
+ put(int.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Integer) obj);
@@ -100,7 +100,7 @@ public Any wrap(Object obj) {
return Any.wrap((int) val);
}
});
- put(Integer.class, new Encoder() {
+ put(Integer.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Integer) obj);
@@ -112,10 +112,10 @@ public Any wrap(Object obj) {
return Any.wrap((int) val);
}
});
- put(char.class, new Encoder() {
+ put(char.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
- stream.writeVal((Integer) obj);
+ stream.writeVal(((Character) obj).charValue());
}
@Override
@@ -124,10 +124,10 @@ public Any wrap(Object obj) {
return Any.wrap((int) val);
}
});
- put(Character.class, new Encoder() {
+ put(Character.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
- stream.writeVal((Integer) obj);
+ stream.writeVal(((Character) obj).charValue());
}
@Override
@@ -136,7 +136,7 @@ public Any wrap(Object obj) {
return Any.wrap((int) val);
}
});
- put(long.class, new Encoder() {
+ put(long.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Long) obj);
@@ -148,7 +148,7 @@ public Any wrap(Object obj) {
return Any.wrap((long) val);
}
});
- put(Long.class, new Encoder() {
+ put(Long.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Long) obj);
@@ -160,7 +160,7 @@ public Any wrap(Object obj) {
return Any.wrap((long) val);
}
});
- put(float.class, new Encoder() {
+ put(float.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Float) obj);
@@ -172,7 +172,7 @@ public Any wrap(Object obj) {
return Any.wrap((float) val);
}
});
- put(Float.class, new Encoder() {
+ put(Float.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Float) obj);
@@ -184,7 +184,7 @@ public Any wrap(Object obj) {
return Any.wrap((float) val);
}
});
- put(double.class, new Encoder() {
+ put(double.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Double) obj);
@@ -196,7 +196,7 @@ public Any wrap(Object obj) {
return Any.wrap((double) val);
}
});
- put(Double.class, new Encoder() {
+ put(Double.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((Double) obj);
@@ -208,7 +208,7 @@ public Any wrap(Object obj) {
return Any.wrap((double) val);
}
});
- put(String.class, new Encoder() {
+ put(String.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeVal((String) obj);
@@ -220,7 +220,7 @@ public Any wrap(Object obj) {
return Any.wrap(val);
}
});
- put(Object.class, new Encoder() {
+ put(Object.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
if (obj != null && obj.getClass() == Object.class) {
@@ -233,17 +233,17 @@ public void encode(Object obj, JsonStream stream) throws IOException {
@Override
public Any wrap(Object obj) {
if (obj != null && obj.getClass() == Object.class) {
- return Any.wrapAnyMap(new HashMap());
+ return Any.rewrap(new HashMap());
}
- return JsonStream.wrap(obj);
+ return CodegenAccess.wrap(obj);
}
});
- put(BigDecimal.class, new Encoder() {
+ put(BigDecimal.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
BigDecimal val = (BigDecimal) obj;
- stream.writeVal(val.toString());
+ stream.writeRaw(val.toString());
}
@Override
@@ -251,11 +251,11 @@ public Any wrap(Object obj) {
return Any.wrap(obj.toString());
}
});
- put(BigInteger.class, new Encoder() {
+ put(BigInteger.class, new Encoder.ReflectionEncoder() {
@Override
public void encode(Object obj, JsonStream stream) throws IOException {
BigInteger val = (BigInteger) obj;
- stream.writeVal(val.toString());
+ stream.writeRaw(val.toString());
}
@Override
@@ -265,17 +265,48 @@ public Any wrap(Object obj) {
});
}};
- public static String genWriteOp(String code, Type valueType) {
- if (NATIVE_ENCODERS.containsKey(valueType)) {
- return String.format("stream.writeVal((%s)%s);", getTypeName(valueType), code);
- }
+ public static void genWriteOp(CodegenResult ctx, String code, Type valueType, boolean isNullable) {
+ genWriteOp(ctx, code, valueType, isNullable, true);
+ }
+ public static void genWriteOp(CodegenResult ctx, String code, Type valueType, boolean isNullable, boolean isCollectionValueNullable) {
+ boolean noIndention = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
String cacheKey = TypeLiteral.create(valueType).getEncoderCacheKey();
+ if (JsoniterSpi.getEncoder(cacheKey) == null) {
+ if (noIndention && !isNullable && String.class == valueType) {
+ ctx.buffer('"');
+ ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeStringWithoutQuote((java.lang.String)%s, stream);", code));
+ ctx.buffer('"');
+ return;
+ }
+ if (NATIVE_ENCODERS.containsKey(valueType)) {
+ ctx.append(String.format("stream.writeVal((%s)%s);", getTypeName(valueType), code));
+ return;
+ }
+ if (valueType instanceof WildcardType) {
+ ctx.append(String.format("stream.writeVal((%s)%s);", getTypeName(Object.class), code));
+ return;
+ }
+ }
+
+ if (!isCollectionValueNullable) {
+ cacheKey = cacheKey + "__value_not_nullable";
+ }
Codegen.getEncoder(cacheKey, valueType);
- if (Codegen.canStaticAccess(cacheKey)) {
- return String.format("%s.encode_((%s)%s, stream);", cacheKey, getTypeName(valueType), code);
+ CodegenResult generatedSource = Codegen.getGeneratedSource(cacheKey);
+ if (generatedSource != null) {
+ if (isNullable) {
+ ctx.appendBuffer();
+ ctx.append(CodegenResult.bufferToWriteOp(generatedSource.prelude));
+ ctx.append(String.format("%s.encode_((%s)%s, stream);", cacheKey, getTypeName(valueType), code));
+ ctx.append(CodegenResult.bufferToWriteOp(generatedSource.epilogue));
+ } else {
+ ctx.buffer(generatedSource.prelude);
+ ctx.append(String.format("%s.encode_((%s)%s, stream);", cacheKey, getTypeName(valueType), code));
+ ctx.buffer(generatedSource.epilogue);
+ }
} else {
- return String.format("com.jsoniter.output.CodegenAccess.writeVal(\"%s\", (%s)%s, stream);", cacheKey, getTypeName(valueType), code);
+ ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeVal(\"%s\", (%s)%s, stream);", cacheKey, getTypeName(valueType), code));
}
}
@@ -287,24 +318,29 @@ public static String getTypeName(Type fieldType) {
ParameterizedType pType = (ParameterizedType) fieldType;
Class clazz = (Class) pType.getRawType();
return clazz.getCanonicalName();
+ } else if (fieldType instanceof WildcardType) {
+ return Object.class.getCanonicalName();
} else {
throw new JsonException("unsupported type: " + fieldType);
}
}
- public static String genEnum(Class clazz) {
- ClassDescriptor desc = JsoniterSpi.getEncodingClassDescriptor(clazz, false);
- StringBuilder lines = new StringBuilder();
- append(lines, String.format("public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {", clazz.getCanonicalName()));
- append(lines, "if (obj == null) { stream.writeNull(); return; }");
- append(lines, "stream.write('\"');");
- append(lines, "stream.writeRaw(obj.toString());");
- append(lines, "stream.write('\"');");
- append(lines, "}");
- return lines.toString();
- }
-
- private static void append(StringBuilder lines, String str) {
- lines.append(str);
- lines.append("\n");
+ public static CodegenResult genEnum(Class clazz) {
+ boolean noIndention = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
+ CodegenResult ctx = new CodegenResult();
+ ctx.append(String.format("public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {", clazz.getCanonicalName()));
+ ctx.append("if (obj == null) { stream.writeNull(); return; }");
+ if (noIndention) {
+ ctx.buffer('"');
+ } else {
+ ctx.append("stream.write('\"');");
+ }
+ ctx.append("stream.writeRaw(obj.toString());");
+ if (noIndention) {
+ ctx.buffer('"');
+ } else {
+ ctx.append("stream.write('\"');");
+ }
+ ctx.append("}");
+ return ctx;
}
}
diff --git a/src/main/java/com/jsoniter/output/CodegenImplObject.java b/src/main/java/com/jsoniter/output/CodegenImplObject.java
index 75a24c3d..2e07f651 100644
--- a/src/main/java/com/jsoniter/output/CodegenImplObject.java
+++ b/src/main/java/com/jsoniter/output/CodegenImplObject.java
@@ -2,47 +2,58 @@
import com.jsoniter.spi.*;
-import java.lang.reflect.Method;
+import java.util.*;
class CodegenImplObject {
- public static String genObject(Class clazz) {
- ClassDescriptor desc = JsoniterSpi.getEncodingClassDescriptor(clazz, false);
- CodegenContext ctx = new CodegenContext();
- ctx.append(String.format("public static void encode_(%s obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {", clazz.getCanonicalName()));
- ctx.append("if (obj == null) { stream.writeNull(); return; }");
+ public static CodegenResult genObject(ClassInfo classInfo) {
+ boolean noIndention = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
+ CodegenResult ctx = new CodegenResult();
+ ClassDescriptor desc = ClassDescriptor.getEncodingClassDescriptor(classInfo, false);
+ List encodeTos = desc.encodeTos();
+ ctx.append(String.format("public static void encode_(%s obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {", classInfo.clazz.getCanonicalName()));
if (hasFieldOutput(desc)) {
- boolean notFirst = false;
- ctx.buffered.append('{');
- for (Binding binding : desc.allEncoderBindings()) {
- for (String toName : binding.toNames) {
- if (notFirst) {
- ctx.buffered.append(',');
- } else {
- notFirst = true;
- }
- ctx.buffered.append("\\\"");
- ctx.buffered.append(toName);
- ctx.buffered.append("\\\"");
- ctx.buffered.append(':');
- genField(ctx, binding);
- }
+ int notFirst = 0;
+ if (noIndention) {
+ ctx.buffer('{');
+ } else {
+ ctx.append("stream.writeObjectStart();");
+ }
+ for (EncodeTo encodeTo : encodeTos) {
+ notFirst = genField(ctx, encodeTo.binding, encodeTo.toName, notFirst);
}
- for (Method unwrapper : desc.unwrappers) {
- if (notFirst) {
- ctx.buffered.append(',');
+ for (UnwrapperDescriptor unwrapper : desc.unwrappers) {
+ if (unwrapper.isMap) {
+ ctx.append(String.format("java.util.Map map = (java.util.Map)obj.%s();", unwrapper.method.getName()));
+ ctx.append("java.util.Iterator iter = map.entrySet().iterator();");
+ ctx.append("while(iter.hasNext()) {");
+ ctx.append("java.util.Map.Entry entry = (java.util.Map.Entry)iter.next();");
+ notFirst = appendComma(ctx, notFirst);
+ ctx.append("stream.writeObjectField(entry.getKey().toString());");
+ ctx.append("if (entry.getValue() == null) { stream.writeNull(); } else {");
+ CodegenImplNative.genWriteOp(ctx, "entry.getValue()", unwrapper.mapValueTypeLiteral.getType(), true);
+ ctx.append("}");
+ ctx.append("}");
} else {
- notFirst = true;
+ notFirst = appendComma(ctx, notFirst);
+ ctx.append(String.format("obj.%s(stream);", unwrapper.method.getName()));
+ }
+ }
+ if (noIndention) {
+ ctx.buffer('}');
+ } else {
+ if (notFirst == 1) { // definitely not first
+ ctx.append("stream.writeObjectEnd();");
+ } else if (notFirst == 2) { // // maybe not first, previous field is omitNull
+ ctx.append("if (notFirst) { stream.writeObjectEnd(); } else { stream.write('}'); }");
+ } else { // this is the first
+ ctx.append("stream.write('}');");
}
- ctx.flushBuffer();
- ctx.append(String.format("obj.%s(stream);", unwrapper.getName()));
}
- ctx.buffered.append('}');
- ctx.flushBuffer();
} else {
- ctx.append("stream.writeEmptyObject();");
+ ctx.buffer("{}");
}
ctx.append("}");
- return ctx.toString().replace("{{clazz}}", clazz.getCanonicalName());
+ return ctx;
}
@@ -50,41 +61,104 @@ private static boolean hasFieldOutput(ClassDescriptor desc) {
if (!desc.unwrappers.isEmpty()) {
return true;
}
- for (Binding binding : desc.allEncoderBindings()) {
- if (binding.toNames.length > 0) {
- return true;
- }
- }
- return false;
+ return !desc.encodeTos().isEmpty();
}
- private static void genField(CodegenContext ctx, Binding binding) {
+ private static int genField(CodegenResult ctx, Binding binding, String toName, int notFirst) {
+ boolean noIndention = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
String fieldCacheKey = binding.encoderCacheKey();
Encoder encoder = JsoniterSpi.getEncoder(fieldCacheKey);
+ boolean isCollectionValueNullable = binding.isCollectionValueNullable;
+ Class valueClazz;
+ String valueAccessor;
if (binding.field != null) {
- if (encoder == null) {
- if (binding.valueType == String.class) {
- ctx.buffered.append("\\\"");
- ctx.flushBuffer();
- ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeStringWithoutQuote(stream, obj.%s);", binding.field.getName()));
- ctx.buffered.append("\\\"");
- } else {
- ctx.flushBuffer();
- ctx.append(CodegenImplNative.genWriteOp("obj." + binding.field.getName(), binding.valueType));
- }
+ valueClazz = binding.field.getType();
+ valueAccessor = "obj." + binding.field.getName();
+ } else {
+ valueClazz = binding.method.getReturnType();
+ valueAccessor = "obj." + binding.method.getName() + "()";
+ }
+ if (!supportCollectionValueNullable(valueClazz)) {
+ isCollectionValueNullable = true;
+ }
+ boolean nullable = !valueClazz.isPrimitive();
+ boolean omitZero = JsoniterSpi.getCurrentConfig().omitDefaultValue();
+ if (!binding.isNullable) {
+ nullable = false;
+ }
+ if (binding.defaultValueToOmit != null) {
+ if (notFirst == 0) { // no previous field
+ notFirst = 2; // maybe
+ ctx.append("boolean notFirst = false;");
+ }
+
+ ctx.append("if (!(" + String.format(binding.defaultValueToOmit.code(), valueAccessor)+ ")) {");
+ notFirst = appendComma(ctx, notFirst);
+ if (noIndention) {
+ ctx.append(CodegenResult.bufferToWriteOp("\"" + toName + "\":"));
} else {
- ctx.flushBuffer();
- ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeVal(\"%s\", obj.%s, stream);",
- fieldCacheKey, binding.field.getName()));
+ ctx.append(String.format("stream.writeObjectField(\"%s\");", toName));
}
} else {
- ctx.flushBuffer();
- if (encoder == null) {
- ctx.append(CodegenImplNative.genWriteOp("obj." + binding.method.getName() + "()", binding.valueType));
+ notFirst = appendComma(ctx, notFirst);
+ if (noIndention) {
+ ctx.buffer('"');
+ ctx.buffer(toName);
+ ctx.buffer('"');
+ ctx.buffer(':');
} else {
- ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeVal(\"%s\", obj.%s(), stream);",
- fieldCacheKey, binding.method.getName()));
+ ctx.append(String.format("stream.writeObjectField(\"%s\");", toName));
+ }
+ if (nullable) {
+ ctx.append(String.format("if (%s == null) { stream.writeNull(); } else {", valueAccessor));
}
}
+ if (encoder == null) {
+ CodegenImplNative.genWriteOp(ctx, valueAccessor, binding.valueType, nullable, isCollectionValueNullable);
+ } else {
+ ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeVal(\"%s\", %s, stream);",
+ fieldCacheKey, valueAccessor));
+ }
+ if (nullable || omitZero) {
+ ctx.append("}");
+ }
+ return notFirst;
+ }
+
+ private static int appendComma(CodegenResult ctx, int notFirst) {
+ boolean noIndention = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
+ if (notFirst == 1) { // definitely not first
+ if (noIndention) {
+ ctx.buffer(',');
+ } else {
+ ctx.append("stream.writeMore();");
+ }
+ } else if (notFirst == 2) { // maybe not first, previous field is omitNull
+ if (noIndention) {
+ ctx.append("if (notFirst) { stream.write(','); } else { notFirst = true; }");
+ } else {
+ ctx.append("if (notFirst) { stream.writeMore(); } else { stream.writeIndention(); notFirst = true; }");
+ }
+ } else { // this is the first, do not write comma
+ notFirst = 1;
+ if (!noIndention) {
+ ctx.append("stream.writeIndention();");
+ }
+ }
+ return notFirst;
+ }
+
+
+ private static boolean supportCollectionValueNullable(Class clazz) {
+ if (clazz.isArray()) {
+ return true;
+ }
+ if (Map.class.isAssignableFrom(clazz)) {
+ return true;
+ }
+ if (Collection.class.isAssignableFrom(clazz)) {
+ return true;
+ }
+ return false;
}
}
diff --git a/src/main/java/com/jsoniter/output/CodegenResult.java b/src/main/java/com/jsoniter/output/CodegenResult.java
new file mode 100644
index 00000000..0271ba2b
--- /dev/null
+++ b/src/main/java/com/jsoniter/output/CodegenResult.java
@@ -0,0 +1,130 @@
+package com.jsoniter.output;
+
+import com.jsoniter.spi.JsoniterSpi;
+
+class CodegenResult {
+
+ private final boolean supportBuffer;
+ String prelude = null; // first
+ String epilogue = null; // last
+ private StringBuilder lines = new StringBuilder();
+ private StringBuilder buffered = new StringBuilder();
+
+ public CodegenResult() {
+ supportBuffer = JsoniterSpi.getCurrentConfig().indentionStep() == 0;
+ }
+
+ public void append(String str) {
+ if (str.contains("stream")) {
+ // maintain the order of write op
+ // must flush now
+ appendBuffer();
+ }
+ lines.append(str);
+ lines.append("\n");
+ }
+
+ public void buffer(char c) {
+ if (supportBuffer) {
+ buffered.append(c);
+ } else {
+ throw new UnsupportedOperationException("internal error: should not call buffer when indention step > 0");
+ }
+ }
+
+ public void buffer(String s) {
+ if (s == null) {
+ return;
+ }
+ if (supportBuffer) {
+ buffered.append(s);
+ } else {
+ throw new UnsupportedOperationException("internal error: should not call buffer when indention step > 0");
+
+ }
+ }
+
+ public void flushBuffer() {
+ if (buffered.length() == 0) {
+ return;
+ }
+ if (prelude == null) {
+ prelude = buffered.toString();
+ } else {
+ epilogue = buffered.toString();
+ }
+ buffered.setLength(0);
+ }
+
+ public String toString() {
+ return lines.toString();
+ }
+
+ public void appendBuffer() {
+ flushBuffer();
+ if (epilogue != null) {
+ lines.append(bufferToWriteOp(epilogue));
+ lines.append("\n");
+ epilogue = null;
+ }
+ }
+
+ public String generateWrapperCode(Class clazz) {
+ flushBuffer();
+ StringBuilder lines = new StringBuilder();
+ append(lines, "public void encode(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {");
+ append(lines, "if (obj == null) { stream.writeNull(); return; }");
+ if (prelude != null) {
+ append(lines, CodegenResult.bufferToWriteOp(prelude));
+ }
+ append(lines, String.format("encode_((%s)obj, stream);", clazz.getCanonicalName()));
+ if (epilogue != null) {
+ append(lines, CodegenResult.bufferToWriteOp(epilogue));
+ }
+ append(lines, "}");
+ return lines.toString();
+ }
+
+ private static void append(StringBuilder lines, String line) {
+ lines.append(line);
+ lines.append('\n');
+ }
+
+ public static String bufferToWriteOp(String buffered) {
+ if (buffered == null) {
+ return "";
+ }
+ if (buffered.length() == 1) {
+ return String.format("stream.write((byte)'%s');", escape(buffered.charAt(0)));
+ } else if (buffered.length() == 2) {
+ return String.format("stream.write((byte)'%s', (byte)'%s');",
+ escape(buffered.charAt(0)), escape(buffered.charAt(1)));
+ } else if (buffered.length() == 3) {
+ return String.format("stream.write((byte)'%s', (byte)'%s', (byte)'%s');",
+ escape(buffered.charAt(0)), escape(buffered.charAt(1)), escape(buffered.charAt(2)));
+ } else if (buffered.length() == 4) {
+ return String.format("stream.write((byte)'%s', (byte)'%s', (byte)'%s', (byte)'%s');",
+ escape(buffered.charAt(0)), escape(buffered.charAt(1)), escape(buffered.charAt(2)), escape(buffered.charAt(3)));
+ } else {
+ StringBuilder escaped = new StringBuilder();
+ for (int i = 0; i < buffered.length(); i++) {
+ char c = buffered.charAt(i);
+ if (c == '"') {
+ escaped.append('\\');
+ }
+ escaped.append(c);
+ }
+ return String.format("stream.writeRaw(\"%s\", %s);", escaped.toString(), buffered.length());
+ }
+ }
+
+ private static String escape(char c) {
+ if (c == '"') {
+ return "\\\"";
+ }
+ if (c == '\\') {
+ return "\\\\";
+ }
+ return String.valueOf(c);
+ }
+}
diff --git a/src/main/java/com/jsoniter/output/DynamicCodegen.java b/src/main/java/com/jsoniter/output/DynamicCodegen.java
index 81eae770..00f07197 100644
--- a/src/main/java/com/jsoniter/output/DynamicCodegen.java
+++ b/src/main/java/com/jsoniter/output/DynamicCodegen.java
@@ -1,26 +1,30 @@
package com.jsoniter.output;
-import com.jsoniter.spi.EmptyEncoder;
import com.jsoniter.spi.Encoder;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.CtMethod;
-import javassist.CtNewMethod;
+import javassist.*;
class DynamicCodegen {
static ClassPool pool = ClassPool.getDefault();
- public static Encoder gen(Class clazz, String cacheKey, String source) throws Exception {
+ static {
+ pool.insertClassPath(new ClassClassPath(Encoder.class));
+ }
+
+ public static Encoder gen(Class clazz, String cacheKey, CodegenResult source) throws Exception {
+ source.flushBuffer();
CtClass ctClass = pool.makeClass(cacheKey);
ctClass.setInterfaces(new CtClass[]{pool.get(Encoder.class.getName())});
- ctClass.setSuperclass(pool.get(EmptyEncoder.class.getName()));
- CtMethod staticMethod = CtNewMethod.make(source, ctClass);
+ String staticCode = source.toString();
+ CtMethod staticMethod = CtNewMethod.make(staticCode, ctClass);
ctClass.addMethod(staticMethod);
- CtMethod interfaceMethod = CtNewMethod.make("" +
- "public void encode(Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {" +
- String.format("return encode_((%s)obj, stream);", clazz.getCanonicalName()) +
- "}", ctClass);
+ String wrapperCode = source.generateWrapperCode(clazz);
+ if ("true".equals(System.getenv("JSONITER_DEBUG"))) {
+ System.out.println(">>> " + cacheKey);
+ System.out.println(wrapperCode);
+ System.out.println(staticCode);
+ }
+ CtMethod interfaceMethod = CtNewMethod.make(wrapperCode, ctClass);
ctClass.addMethod(interfaceMethod);
return (Encoder) ctClass.toClass().newInstance();
}
diff --git a/src/main/java/com/jsoniter/output/JsonStream.java b/src/main/java/com/jsoniter/output/JsonStream.java
index 9b396c48..7886bc05 100644
--- a/src/main/java/com/jsoniter/output/JsonStream.java
+++ b/src/main/java/com/jsoniter/output/JsonStream.java
@@ -1,23 +1,17 @@
package com.jsoniter.output;
-import com.jsoniter.JsonException;
import com.jsoniter.any.Any;
-import com.jsoniter.spi.Encoder;
-import com.jsoniter.spi.TypeLiteral;
+import com.jsoniter.spi.*;
import java.io.IOException;
import java.io.OutputStream;
+import java.lang.reflect.Type;
public class JsonStream extends OutputStream {
- public static int defaultIndentionStep = 0;
- public int indentionStep = defaultIndentionStep;
- private int indention = 0;
+ public Config configCache;
+ int indention = 0;
private OutputStream out;
- char[] reusableChars = new char[32];
- private static final byte[] NULL = "null".getBytes();
- private static final byte[] TRUE = "true".getBytes();
- private static final byte[] FALSE = "false".getBytes();
byte buf[];
int count;
@@ -34,24 +28,86 @@ public void reset(OutputStream out) {
this.count = 0;
}
- public final void write(int b) throws IOException {
- if (count >= buf.length) {
- flushBuffer();
+ final void ensure(int minimal) throws IOException {
+ int available = buf.length - count;
+ if (available < minimal) {
+ if (count > 1024) {
+ flushBuffer();
+ }
+ growAtLeast(minimal);
}
+ }
+
+ private final void growAtLeast(int minimal) {
+ int toGrow = buf.length;
+ if (toGrow < minimal) {
+ toGrow = minimal;
+ }
+ byte[] newBuf = new byte[buf.length + toGrow];
+ System.arraycopy(buf, 0, newBuf, 0, buf.length);
+ buf = newBuf;
+ }
+
+ public final void write(int b) throws IOException {
+ ensure(1);
buf[count++] = (byte) b;
}
+ public final void write(byte b1, byte b2) throws IOException {
+ ensure(2);
+ buf[count++] = b1;
+ buf[count++] = b2;
+ }
+
+ public final void write(byte b1, byte b2, byte b3) throws IOException {
+ ensure(3);
+ buf[count++] = b1;
+ buf[count++] = b2;
+ buf[count++] = b3;
+ }
+
+ public final void write(byte b1, byte b2, byte b3, byte b4) throws IOException {
+ ensure(4);
+ buf[count++] = b1;
+ buf[count++] = b2;
+ buf[count++] = b3;
+ buf[count++] = b4;
+ }
+
+ public final void write(byte b1, byte b2, byte b3, byte b4, byte b5) throws IOException {
+ ensure(5);
+ buf[count++] = b1;
+ buf[count++] = b2;
+ buf[count++] = b3;
+ buf[count++] = b4;
+ buf[count++] = b5;
+ }
+
+ public final void write(byte b1, byte b2, byte b3, byte b4, byte b5, byte b6) throws IOException {
+ ensure(6);
+ buf[count++] = b1;
+ buf[count++] = b2;
+ buf[count++] = b3;
+ buf[count++] = b4;
+ buf[count++] = b5;
+ buf[count++] = b6;
+ }
+
public final void write(byte b[], int off, int len) throws IOException {
- if (len >= buf.length) {
+ if (out == null) {
+ ensure(len);
+ } else {
+ if (len >= buf.length - count) {
+ if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
- flushBuffer();
- out.write(b, off, len);
- return;
- }
- if (len > buf.length - count) {
- flushBuffer();
+ flushBuffer();
+ out.write(b, off, len);
+ return;
+ }
+ flushBuffer();
+ }
}
System.arraycopy(b, off, buf, count, len);
count += len;
@@ -64,6 +120,9 @@ public void flush() throws IOException {
@Override
public void close() throws IOException {
+ if (out == null) {
+ return;
+ }
if (count > 0) {
flushBuffer();
}
@@ -73,6 +132,9 @@ public void close() throws IOException {
}
final void flushBuffer() throws IOException {
+ if (out == null) {
+ return;
+ }
out.write(buf, 0, count);
count = 0;
}
@@ -81,16 +143,23 @@ public final void writeVal(String val) throws IOException {
if (val == null) {
writeNull();
} else {
- write('"');
StreamImplString.writeString(this, val);
- write('"');
}
}
public final void writeRaw(String val) throws IOException {
+ writeRaw(val, val.length());
+ }
+
+ public final void writeRaw(String val, int remaining) throws IOException {
+ if (out == null) {
+ ensure(remaining);
+ val.getBytes(0, remaining, buf, count);
+ count += remaining;
+ return;
+ }
int i = 0;
- int remaining = val.length();
- for(;;) {
+ for (; ; ) {
int available = buf.length - count;
if (available < remaining) {
remaining -= available;
@@ -129,18 +198,18 @@ public final void writeVal(boolean val) throws IOException {
}
public final void writeTrue() throws IOException {
- write(TRUE);
+ write((byte) 't', (byte) 'r', (byte) 'u', (byte) 'e');
}
public final void writeFalse() throws IOException {
- write(FALSE);
+ write((byte) 'f', (byte) 'a', (byte) 'l', (byte) 's', (byte) 'e');
}
public final void writeVal(Short val) throws IOException {
if (val == null) {
writeNull();
} else {
- write(val.intValue());
+ writeVal(val.intValue());
}
}
@@ -203,23 +272,20 @@ public final void writeVal(Any val) throws IOException {
}
public final void writeNull() throws IOException {
- write(NULL, 0, NULL.length);
+ write((byte) 'n', (byte) 'u', (byte) 'l', (byte) 'l');
}
public final void writeEmptyObject() throws IOException {
- write('{');
- write('}');
+ write((byte) '{', (byte) '}');
}
public final void writeEmptyArray() throws IOException {
- write('[');
- write(']');
+ write((byte) '[', (byte) ']');
}
public final void writeArrayStart() throws IOException {
- indention += indentionStep;
+ indention += currentConfig().indentionStep();
write('[');
- writeIndention();
}
public final void writeMore() throws IOException {
@@ -227,7 +293,7 @@ public final void writeMore() throws IOException {
writeIndention();
}
- private void writeIndention() throws IOException {
+ public void writeIndention() throws IOException {
writeIndention(0);
}
@@ -237,37 +303,50 @@ private void writeIndention(int delta) throws IOException {
}
write('\n');
int toWrite = indention - delta;
- int i = 0;
- for (; ; ) {
- for (; i < toWrite && count < buf.length; i++) {
- buf[count++] = ' ';
- }
- if (i == toWrite) {
- break;
- } else {
- flushBuffer();
- }
+ ensure(toWrite);
+ for (int i = 0; i < toWrite && count < buf.length; i++) {
+ buf[count++] = ' ';
}
}
public final void writeArrayEnd() throws IOException {
+ int indentionStep = currentConfig().indentionStep();
writeIndention(indentionStep);
indention -= indentionStep;
write(']');
}
public final void writeObjectStart() throws IOException {
+ int indentionStep = currentConfig().indentionStep();
indention += indentionStep;
write('{');
- writeIndention();
}
public final void writeObjectField(String field) throws IOException {
writeVal(field);
- write(':');
+ if (indention > 0) {
+ write((byte) ':', (byte) ' ');
+ } else {
+ write(':');
+ }
+ }
+
+ public final void writeObjectField(Object key) throws IOException {
+ Encoder encoder = MapKeyEncoders.registerOrGetExisting(key.getClass());
+ writeObjectField(key, encoder);
+ }
+
+ public final void writeObjectField(Object key, Encoder keyEncoder) throws IOException {
+ keyEncoder.encode(key, this);
+ if (indention > 0) {
+ write((byte) ':', (byte) ' ');
+ } else {
+ write(':');
+ }
}
public final void writeObjectEnd() throws IOException {
+ int indentionStep = currentConfig().indentionStep();
writeIndention(indentionStep);
indention -= indentionStep;
write('}');
@@ -279,23 +358,50 @@ public final void writeVal(Object obj) throws IOException {
return;
}
Class> clazz = obj.getClass();
- String cacheKey = TypeLiteral.create(clazz).getEncoderCacheKey();
+ String cacheKey = currentConfig().getEncoderCacheKey(clazz);
Codegen.getEncoder(cacheKey, clazz).encode(obj, this);
}
public final void writeVal(TypeLiteral typeLiteral, T obj) throws IOException {
- Codegen.getEncoder(typeLiteral.getEncoderCacheKey(), typeLiteral.getType()).encode(obj, this);
+ if (null == obj) {
+ writeNull();
+ } else {
+ Config config = currentConfig();
+ String cacheKey = config.getEncoderCacheKey(typeLiteral.getType());
+ Codegen.getEncoder(cacheKey, typeLiteral.getType()).encode(obj, this);
+ }
+ }
+
+ public final void writeVal(Type type, T obj) throws IOException {
+ if (null == obj) {
+ writeNull();
+ } else {
+ Config config = currentConfig();
+ String cacheKey = config.getEncoderCacheKey(type);
+ Codegen.getEncoder(cacheKey, type).encode(obj, this);
+ }
}
- private final static ThreadLocal tlsStream = new ThreadLocal() {
- @Override
- protected JsonStream initialValue() {
- return new JsonStream(null, 4096);
+ public Config currentConfig() {
+ if (configCache != null) {
+ return configCache;
}
- };
+ configCache = JsoniterSpi.getCurrentConfig();
+ return configCache;
+ }
+
+ public static void serialize(Config config, Object obj, OutputStream out) {
+ JsoniterSpi.setCurrentConfig(config);
+ try {
+ serialize(obj, out);
+ } finally {
+ JsoniterSpi.clearCurrentConfig();
+ }
+
+ }
public static void serialize(Object obj, OutputStream out) {
- JsonStream stream = tlsStream.get();
+ JsonStream stream = JsonStreamPool.borrowJsonStream();
try {
try {
stream.reset(out);
@@ -305,37 +411,120 @@ public static void serialize(Object obj, OutputStream out) {
}
} catch (IOException e) {
throw new JsonException(e);
+ } finally {
+ JsonStreamPool.returnJsonStream(stream);
+ }
+ }
+
+ public static void serialize(Config config, TypeLiteral typeLiteral, Object obj, OutputStream out) {
+ JsoniterSpi.setCurrentConfig(config);
+ try {
+ serialize(typeLiteral, obj, out);
+ } finally {
+ JsoniterSpi.clearCurrentConfig();
}
}
- private final static ThreadLocal tlsAsciiOutputStream = new ThreadLocal() {
- @Override
- protected AsciiOutputStream initialValue() {
- return new AsciiOutputStream();
+ public static void serialize(TypeLiteral typeLiteral, Object obj, OutputStream out) {
+ JsonStream stream = JsonStreamPool.borrowJsonStream();
+ try {
+ try {
+ stream.reset(out);
+ stream.writeVal(typeLiteral, obj);
+ } finally {
+ stream.close();
+ }
+ } catch (IOException e) {
+ throw new JsonException(e);
+ } finally {
+ JsonStreamPool.returnJsonStream(stream);
}
- };
+ }
+
+ public static void serialize(Type type, Object obj, OutputStream out) {
+ serialize(type, obj, out, false);
+ }
+
+ public static String serialize(Config config, Object obj) {
+ return serialize(config, obj.getClass(), obj);
+ }
public static String serialize(Object obj) {
- AsciiOutputStream asciiOutputStream = tlsAsciiOutputStream.get();
- asciiOutputStream.reset();
- serialize(obj, asciiOutputStream);
- return asciiOutputStream.toString();
+ return serialize(obj.getClass(), obj);
}
- public static void setMode(EncodingMode mode) {
- Codegen.setMode(mode);
+ public static String serialize(Config config, TypeLiteral typeLiteral, Object obj) {
+ return serialize(config, typeLiteral.getType(), obj);
}
- public static Any wrap(Object val) {
- if (val == null) {
- return Any.wrapNull();
+ private static String serialize(Config config, Type type, Object obj) {
+ final Config configBackup = JsoniterSpi.getCurrentConfig();
+ // Set temporary config
+ JsoniterSpi.setCurrentConfig(config);
+ try {
+ return serialize(type, obj);
+ } finally {
+ // Revert old config
+ JsoniterSpi.setCurrentConfig(configBackup);
+ }
+ }
+
+ public static String serialize(TypeLiteral typeLiteral, Object obj) {
+ return serialize(typeLiteral.getType(), obj);
+ }
+
+ public static String serialize(boolean escapeUnicode, Type type, Object obj) {
+ final Config currentConfig = JsoniterSpi.getCurrentConfig();
+ return serialize(currentConfig.copyBuilder().escapeUnicode(escapeUnicode).build(), type, obj);
+ }
+
+ private static String serialize(Type type, Object obj) {
+ return serialize(type, obj, null, true);
+ }
+
+ private static String serialize(Type type, Object obj, OutputStream out, boolean returnObjAsString) {
+ final JsonStream stream = JsonStreamPool.borrowJsonStream();
+ final boolean escapeUnicode = JsoniterSpi.getCurrentConfig().escapeUnicode();
+ try {
+ try {
+ stream.reset(out);
+ stream.writeVal(type, obj);
+ } finally {
+ stream.close();
+ }
+ if (!returnObjAsString) {
+ return "";
+ }
+ if (escapeUnicode) {
+ return new String(stream.buf, 0, stream.count);
+ } else {
+ return new String(stream.buf, 0, stream.count, "UTF8");
+ }
+ } catch (IOException e) {
+ throw new JsonException(e);
+ } finally {
+ JsonStreamPool.returnJsonStream(stream);
}
- Class> clazz = val.getClass();
- String cacheKey = TypeLiteral.create(clazz).getEncoderCacheKey();
- return Codegen.getReflectionEncoder(cacheKey, clazz).wrap(val);
}
- public static void registerNativeEncoder(Class clazz, Encoder encoder) {
+ public static void setMode(EncodingMode mode) {
+ Config newConfig = JsoniterSpi.getDefaultConfig().copyBuilder().encodingMode(mode).build();
+ JsoniterSpi.setDefaultConfig(newConfig);
+ JsoniterSpi.setCurrentConfig(newConfig);
+
+ }
+
+ public static void setIndentionStep(int indentionStep) {
+ Config newConfig = JsoniterSpi.getDefaultConfig().copyBuilder().indentionStep(indentionStep).build();
+ JsoniterSpi.setDefaultConfig(newConfig);
+ JsoniterSpi.setCurrentConfig(newConfig);
+ }
+
+ public static void registerNativeEncoder(Class clazz, Encoder.ReflectionEncoder encoder) {
CodegenImplNative.NATIVE_ENCODERS.put(clazz, encoder);
}
+
+ public Slice buffer() {
+ return new Slice(buf, 0, count);
+ }
}
diff --git a/src/main/java/com/jsoniter/output/JsonStreamPool.java b/src/main/java/com/jsoniter/output/JsonStreamPool.java
new file mode 100644
index 00000000..5a2acb87
--- /dev/null
+++ b/src/main/java/com/jsoniter/output/JsonStreamPool.java
@@ -0,0 +1,35 @@
+package com.jsoniter.output;
+
+public class JsonStreamPool {
+
+ private final static ThreadLocal slot1 = new ThreadLocal();
+ private final static ThreadLocal slot2 = new ThreadLocal();
+
+ public static JsonStream borrowJsonStream() {
+ JsonStream stream = slot1.get();
+ if (stream != null) {
+ slot1.set(null);
+ return stream;
+ }
+ stream = slot2.get();
+ if (stream != null) {
+ slot2.set(null);
+ return stream;
+ }
+ return new JsonStream(null, 512);
+ }
+
+ public static void returnJsonStream(JsonStream jsonStream) {
+ jsonStream.configCache = null;
+ jsonStream.indention = 0;
+ if (slot1.get() == null) {
+ slot1.set(jsonStream);
+ return;
+ }
+ if (slot2.get() == null) {
+ slot2.set(jsonStream);
+ return;
+ }
+ }
+
+}
diff --git a/src/main/java/com/jsoniter/output/MapKeyEncoders.java b/src/main/java/com/jsoniter/output/MapKeyEncoders.java
new file mode 100644
index 00000000..401ebfbe
--- /dev/null
+++ b/src/main/java/com/jsoniter/output/MapKeyEncoders.java
@@ -0,0 +1,78 @@
+package com.jsoniter.output;
+
+import com.jsoniter.spi.*;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+
+class MapKeyEncoders {
+
+ public static Encoder registerOrGetExisting(Type mapKeyType) {
+ String cacheKey = JsoniterSpi.getMapKeyEncoderCacheKey(mapKeyType);
+ Encoder mapKeyEncoder = JsoniterSpi.getMapKeyEncoder(cacheKey);
+ if (null != mapKeyEncoder) {
+ return mapKeyEncoder;
+ }
+ mapKeyEncoder = createDefaultEncoder(mapKeyType);
+ JsoniterSpi.addNewMapEncoder(cacheKey, mapKeyEncoder);
+ return mapKeyEncoder;
+ }
+
+ private static Encoder createDefaultEncoder(Type mapKeyType) {
+ if (mapKeyType == String.class) {
+ return new StringKeyEncoder();
+ }
+ if (mapKeyType == Object.class) {
+ return new DynamicKeyEncoder();
+ }
+ if (mapKeyType instanceof WildcardType) {
+ return new DynamicKeyEncoder();
+ }
+ if (mapKeyType instanceof Class && ((Class) mapKeyType).isEnum()) {
+ return new StringKeyEncoder();
+ }
+ Encoder.ReflectionEncoder encoder = CodegenImplNative.NATIVE_ENCODERS.get(mapKeyType);
+ if (encoder != null) {
+ return new NumberKeyEncoder(encoder);
+ }
+ throw new JsonException("can not encode map key type: " + mapKeyType);
+ }
+
+ private static class StringKeyEncoder implements Encoder {
+
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ stream.writeVal(obj);
+ }
+ }
+
+ private static class NumberKeyEncoder implements Encoder {
+
+ private final Encoder encoder;
+
+ private NumberKeyEncoder(Encoder encoder) {
+ this.encoder = encoder;
+ }
+
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ stream.write('"');
+ encoder.encode(obj, stream);
+ stream.write('"');
+ }
+ }
+
+ private static class DynamicKeyEncoder implements Encoder {
+
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ Class> clazz = obj.getClass();
+ if (clazz == Object.class) {
+ throw new JsonException("map key type is Object.class, can not be encoded");
+ }
+ Encoder mapKeyEncoder = registerOrGetExisting(clazz);
+ mapKeyEncoder.encode(obj, stream);
+ }
+ }
+}
diff --git a/src/main/java/com/jsoniter/output/ReflectionArrayEncoder.java b/src/main/java/com/jsoniter/output/ReflectionArrayEncoder.java
index 9b3a3d5f..bac1dbae 100644
--- a/src/main/java/com/jsoniter/output/ReflectionArrayEncoder.java
+++ b/src/main/java/com/jsoniter/output/ReflectionArrayEncoder.java
@@ -7,9 +7,8 @@
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
-import java.util.ArrayList;
-class ReflectionArrayEncoder implements Encoder {
+class ReflectionArrayEncoder implements Encoder.ReflectionEncoder {
private final TypeLiteral compTypeLiteral;
@@ -29,6 +28,7 @@ public void encode(Object obj, JsonStream stream) throws IOException {
return;
}
stream.writeArrayStart();
+ stream.writeIndention();
stream.writeVal(compTypeLiteral, Array.get(obj, 0));
for (int i = 1; i < len; i++) {
stream.writeMore();
@@ -39,11 +39,6 @@ public void encode(Object obj, JsonStream stream) throws IOException {
@Override
public Any wrap(Object obj) {
- int len = Array.getLength(obj);
- ArrayList copied = new ArrayList(len);
- for (int i = 0; i < len; i++) {
- copied.add(JsonStream.wrap(Array.get(obj, i)));
- }
- return Any.wrapAnyList(copied);
+ return Any.wrapArray(obj);
}
}
diff --git a/src/main/java/com/jsoniter/output/ReflectionCollectionEncoder.java b/src/main/java/com/jsoniter/output/ReflectionCollectionEncoder.java
index 0d7d5a38..9479d0a7 100644
--- a/src/main/java/com/jsoniter/output/ReflectionCollectionEncoder.java
+++ b/src/main/java/com/jsoniter/output/ReflectionCollectionEncoder.java
@@ -9,7 +9,7 @@
import java.util.Collection;
import java.util.Iterator;
-class ReflectionCollectionEncoder implements Encoder {
+class ReflectionCollectionEncoder implements Encoder.ReflectionEncoder {
private final TypeLiteral compTypeLiteral;
@@ -34,6 +34,7 @@ public void encode(Object obj, JsonStream stream) throws IOException {
return;
}
stream.writeArrayStart();
+ stream.writeIndention();
stream.writeVal(compTypeLiteral, iter.next());
while (iter.hasNext()) {
stream.writeMore();
diff --git a/src/main/java/com/jsoniter/output/ReflectionEncoderFactory.java b/src/main/java/com/jsoniter/output/ReflectionEncoderFactory.java
index 39191151..3825225d 100644
--- a/src/main/java/com/jsoniter/output/ReflectionEncoderFactory.java
+++ b/src/main/java/com/jsoniter/output/ReflectionEncoderFactory.java
@@ -1,17 +1,24 @@
package com.jsoniter.output;
+import com.jsoniter.spi.ClassInfo;
import com.jsoniter.spi.Encoder;
import java.lang.reflect.Type;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
public class ReflectionEncoderFactory {
- public static Encoder create(Class clazz, Type... typeArgs) {
+ public static Encoder.ReflectionEncoder create(ClassInfo classInfo) {
+ Class clazz = classInfo.clazz;
+ Type[] typeArgs = classInfo.typeArgs;
if (clazz.isArray()) {
return new ReflectionArrayEncoder(clazz, typeArgs);
}
+ if (List.class.isAssignableFrom(clazz)) {
+ return new ReflectionListEncoder(clazz, typeArgs);
+ }
if (Collection.class.isAssignableFrom(clazz)) {
return new ReflectionCollectionEncoder(clazz, typeArgs);
}
@@ -21,6 +28,6 @@ public static Encoder create(Class clazz, Type... typeArgs) {
if (clazz.isEnum()) {
return new ReflectionEnumEncoder(clazz);
}
- return new ReflectionObjectEncoder(clazz);
+ return new ReflectionObjectEncoder(classInfo);
}
}
diff --git a/src/main/java/com/jsoniter/output/ReflectionEnumEncoder.java b/src/main/java/com/jsoniter/output/ReflectionEnumEncoder.java
index 3c1c547f..4324e6c9 100644
--- a/src/main/java/com/jsoniter/output/ReflectionEnumEncoder.java
+++ b/src/main/java/com/jsoniter/output/ReflectionEnumEncoder.java
@@ -5,7 +5,7 @@
import java.io.IOException;
-class ReflectionEnumEncoder implements Encoder {
+class ReflectionEnumEncoder implements Encoder.ReflectionEncoder {
public ReflectionEnumEncoder(Class clazz) {
}
diff --git a/src/main/java/com/jsoniter/output/ReflectionListEncoder.java b/src/main/java/com/jsoniter/output/ReflectionListEncoder.java
new file mode 100644
index 00000000..bfeccafa
--- /dev/null
+++ b/src/main/java/com/jsoniter/output/ReflectionListEncoder.java
@@ -0,0 +1,49 @@
+package com.jsoniter.output;
+
+import com.jsoniter.any.Any;
+import com.jsoniter.spi.Encoder;
+import com.jsoniter.spi.TypeLiteral;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.List;
+
+class ReflectionListEncoder implements Encoder.ReflectionEncoder {
+
+ private final TypeLiteral compTypeLiteral;
+
+ public ReflectionListEncoder(Class clazz, Type[] typeArgs) {
+ if (typeArgs.length > 0) {
+ compTypeLiteral = TypeLiteral.create(typeArgs[0]);
+ } else {
+ compTypeLiteral = TypeLiteral.create(Object.class);
+ }
+ }
+
+ @Override
+ public void encode(Object obj, JsonStream stream) throws IOException {
+ if (null == obj) {
+ stream.writeNull();
+ return;
+ }
+ List list = (List) obj;
+ if (list.isEmpty()) {
+ stream.writeEmptyArray();
+ return;
+ }
+ stream.writeArrayStart();
+ stream.writeIndention();
+ stream.writeVal(compTypeLiteral, list.get(0));
+ for (int i = 1; i < list.size(); i++) {
+ stream.writeMore();
+ stream.writeVal(compTypeLiteral, list.get(i));
+ }
+ stream.writeArrayEnd();
+ }
+
+ @Override
+ public Any wrap(Object obj) {
+ List col = (List) obj;
+ return Any.wrap(col);
+ }
+}
diff --git a/src/main/java/com/jsoniter/output/ReflectionMapEncoder.java b/src/main/java/com/jsoniter/output/ReflectionMapEncoder.java
index e8a8253d..ddf27d65 100644
--- a/src/main/java/com/jsoniter/output/ReflectionMapEncoder.java
+++ b/src/main/java/com/jsoniter/output/ReflectionMapEncoder.java
@@ -1,24 +1,27 @@
package com.jsoniter.output;
import com.jsoniter.any.Any;
-import com.jsoniter.spi.Encoder;
-import com.jsoniter.spi.TypeLiteral;
+import com.jsoniter.spi.*;
import java.io.IOException;
import java.lang.reflect.Type;
-import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
-class ReflectionMapEncoder implements Encoder {
+class ReflectionMapEncoder implements Encoder.ReflectionEncoder {
private final TypeLiteral valueTypeLiteral;
+ private final Encoder mapKeyEncoder;
public ReflectionMapEncoder(Class clazz, Type[] typeArgs) {
- if (typeArgs.length > 1) {
- valueTypeLiteral = TypeLiteral.create(typeArgs[1]);
- } else {
- valueTypeLiteral = TypeLiteral.create(Object.class);
+ Type keyType = Object.class;
+ Type valueType = Object.class;
+ if (typeArgs.length == 2) {
+ keyType = typeArgs[0];
+ valueType = typeArgs[1];
}
+ mapKeyEncoder = MapKeyEncoders.registerOrGetExisting(keyType);
+ valueTypeLiteral = TypeLiteral.create(valueType);
}
@Override
@@ -27,28 +30,38 @@ public void encode(Object obj, JsonStream stream) throws IOException {
stream.writeNull();
return;
}
- Map map = (Map) obj;
+ Map map = (Map) obj;
+ Iterator> iter = map.entrySet().iterator();
+ if (!iter.hasNext()) {
+ stream.write((byte) '{', (byte) '}');
+ return;
+ }
stream.writeObjectStart();
boolean notFirst = false;
- for (Map.Entry entry : map.entrySet()) {
- if (notFirst) {
- stream.writeMore();
- } else {
- notFirst = true;
- }
- stream.writeObjectField(entry.getKey());
- stream.writeVal(valueTypeLiteral, entry.getValue());
+ Map.Entry entry = iter.next();
+ notFirst = writeEntry(stream, notFirst, entry);
+ while (iter.hasNext()) {
+ entry = iter.next();
+ notFirst = writeEntry(stream, notFirst, entry);
}
stream.writeObjectEnd();
}
+ private boolean writeEntry(JsonStream stream, boolean notFirst, Map.Entry entry) throws IOException {
+ if (notFirst) {
+ stream.writeMore();
+ } else {
+ stream.writeIndention();
+ notFirst = true;
+ }
+ stream.writeObjectField(entry.getKey(), mapKeyEncoder);
+ stream.writeVal(valueTypeLiteral, entry.getValue());
+ return notFirst;
+ }
+
@Override
public Any wrap(Object obj) {
Map map = (Map) obj;
- Map