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) +[![Build Status](https://travis-ci.org/json-iterator/java.svg?branch=master)](https://travis-ci.org/json-iterator/java) +[![codecov](https://codecov.io/gh/json-iterator/java/branch/master/graph/badge.svg)](https://codecov.io/gh/json-iterator/java) +[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/json-iterator/java/master/LICENSE) +[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](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 - -![java1](http://jsoniter.com/benchmarks/java1.png) - -# 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 [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](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 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 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 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 decoder() { + return Decoder.class; + } + + @Override + public Class implementation() { + return Object.class; + } + + @Override + public Class encoder() { + return Encoder.class; + } + + @Override + public boolean nullable() { + return true; + } + + @Override + public boolean collectionValueNullable() { + return true; + } + + @Override + public String defaultValueToOmit() { + return ""; + } + + @Override + public Class 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 annotationType() { + return JsonIgnore.class; + } + }; + } + return new JsonIgnore() { + @Override + public boolean ignoreDecoding() { + return true; + } + + @Override + public boolean ignoreEncoding() { + return true; + } + + @Override + public Class 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 encoder() { return Encoder.class; } + @Override + public boolean nullable() { + return true; + } + + @Override + public boolean collectionValueNullable() { + return true; + } + + @Override + public String defaultValueToOmit() { + return ""; + } + @Override public Class annotationType() { return JsonProperty.class; @@ -108,4 +147,45 @@ public Class 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 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 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 copied = new HashMap(); - for (Map.Entry entry : map.entrySet()) { - copied.put(entry.getKey(), JsonStream.wrap(entry.getValue())); - } - return Any.wrapAnyMap(copied); + return Any.wrap(map); } } diff --git a/src/main/java/com/jsoniter/output/ReflectionObjectEncoder.java b/src/main/java/com/jsoniter/output/ReflectionObjectEncoder.java index 56af8374..99c256c3 100644 --- a/src/main/java/com/jsoniter/output/ReflectionObjectEncoder.java +++ b/src/main/java/com/jsoniter/output/ReflectionObjectEncoder.java @@ -1,27 +1,33 @@ package com.jsoniter.output; -import com.jsoniter.JsonException; +import com.jsoniter.spi.*; import com.jsoniter.any.Any; -import com.jsoniter.spi.Binding; -import com.jsoniter.spi.ClassDescriptor; -import com.jsoniter.spi.Encoder; -import com.jsoniter.spi.JsoniterSpi; import java.io.IOException; -import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; -class ReflectionObjectEncoder implements Encoder { +class ReflectionObjectEncoder implements Encoder.ReflectionEncoder { private final ClassDescriptor desc; + private final List fields = new ArrayList(); + private final List getters = new ArrayList(); - public ReflectionObjectEncoder(Class clazz) { - desc = JsoniterSpi.getEncodingClassDescriptor(clazz, true); - for (Binding binding : desc.allEncoderBindings()) { + public ReflectionObjectEncoder(ClassInfo classInfo) { + desc = ClassDescriptor.getEncodingClassDescriptor(classInfo, true); + for (EncodeTo encodeTo : desc.encodeTos()) { + Binding binding = encodeTo.binding; if (binding.encoder == null) { // the field encoder might be registered directly binding.encoder = JsoniterSpi.getEncoder(binding.encoderCacheKey()); } + if (binding.field != null) { + fields.add(encodeTo); + } else { + getters.add(encodeTo); + } } } @@ -29,6 +35,8 @@ public ReflectionObjectEncoder(Class clazz) { public void encode(Object obj, JsonStream stream) throws IOException { try { enocde_(obj, stream); + } catch (JsonException e) { + throw e; } catch (Exception e) { throw new JsonException(e); } @@ -36,24 +44,22 @@ public void encode(Object obj, JsonStream stream) throws IOException { @Override public Any wrap(Object obj) { - HashMap copied = new HashMap(); + HashMap copied = new HashMap(); try { - for (Binding field : desc.fields) { - Object val = field.field.get(obj); - for (String toName : field.toNames) { - copied.put(toName, JsonStream.wrap(val)); - } + for (EncodeTo encodeTo : fields) { + Object val = encodeTo.binding.field.get(obj); + copied.put(encodeTo.toName, val); } - for (Binding getter : desc.getters) { - Object val = getter.method.invoke(obj); - for (String toName : getter.toNames) { - copied.put(toName, JsonStream.wrap(val)); - } + for (EncodeTo getter : getters) { + Object val = getter.binding.method.invoke(obj); + copied.put(getter.toName, val); } + } catch (JsonException e) { + throw e; } catch (Exception e) { throw new JsonException(e); } - return Any.wrapAnyMap(copied); + return Any.wrap(copied); } private void enocde_(Object obj, JsonStream stream) throws Exception { @@ -63,46 +69,58 @@ private void enocde_(Object obj, JsonStream stream) throws Exception { } stream.writeObjectStart(); boolean notFirst = false; - for (Binding field : desc.fields) { - Object val = field.field.get(obj); - for (String toName : field.toNames) { - if (notFirst) { - stream.writeMore(); - } else { - notFirst = true; - } - stream.writeObjectField(toName); - if (field.encoder != null) { - field.encoder.encode(val, stream); - } else { - stream.writeVal(val); - } - } + for (EncodeTo encodeTo : fields) { + Object val = encodeTo.binding.field.get(obj); + notFirst = writeEncodeTo(stream, notFirst, encodeTo, val); + } + for (EncodeTo encodeTo : getters) { + Object val = encodeTo.binding.method.invoke(obj); + notFirst = writeEncodeTo(stream, notFirst, encodeTo, val); } - for (Binding getter : desc.getters) { - Object val = getter.method.invoke(obj); - for (String toName : getter.toNames) { + for (UnwrapperDescriptor unwrapper : desc.unwrappers) { + if (unwrapper.isMap) { + Map map = (Map) unwrapper.method.invoke(obj); + for (Map.Entry entry : map.entrySet()) { + if (notFirst) { + stream.writeMore(); + } else { + notFirst = true; + } + stream.writeObjectField(entry.getKey().toString()); + stream.writeVal(unwrapper.mapValueTypeLiteral, entry.getValue()); + } + } else { if (notFirst) { stream.writeMore(); } else { notFirst = true; } - stream.writeObjectField(toName); - if (getter.encoder != null) { - getter.encoder.encode(val, stream); - } else { - stream.writeVal(val); - } + unwrapper.method.invoke(obj, stream); } } - for (Method unwrapper : desc.unwrappers) { + if (notFirst) { + stream.writeObjectEnd(); + } else { + stream.write('}'); + } + } + + private boolean writeEncodeTo(JsonStream stream, boolean notFirst, EncodeTo encodeTo, Object val) throws IOException { + OmitValue defaultValueToOmit = encodeTo.binding.defaultValueToOmit; + if (!(defaultValueToOmit != null && defaultValueToOmit.shouldOmit(val))) { if (notFirst) { stream.writeMore(); } else { + stream.writeIndention(); notFirst = true; } - unwrapper.invoke(obj, stream); + stream.writeObjectField(encodeTo.toName); + if (encodeTo.binding.encoder != null) { + encodeTo.binding.encoder.encode(val, stream); + } else { + stream.writeVal(val); + } } - stream.writeObjectEnd(); + return notFirst; } } diff --git a/src/main/java/com/jsoniter/output/StreamImplNumber.java b/src/main/java/com/jsoniter/output/StreamImplNumber.java index cb8ff4e1..54e039c6 100644 --- a/src/main/java/com/jsoniter/output/StreamImplNumber.java +++ b/src/main/java/com/jsoniter/output/StreamImplNumber.java @@ -1,175 +1,242 @@ +/* +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.output; import java.io.IOException; class StreamImplNumber { - private final static byte[] DigitTens = { - '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', - '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', - '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', - '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', - '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', - '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', - '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', - '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', - '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', - '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', - }; - - private final static byte[] DigitOnes = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - }; - - /** - * All possible chars for representing a number as a String - */ - private final static byte[] digits = { - '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', 'a', 'b', - 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z' - }; - private static final byte[] INT_MIN = "-2147483648".getBytes(); - private static final byte[] LONG_MIN = "-9223372036854775808".getBytes(); - - public static final void writeInt(JsonStream stream, int val) throws IOException { - if (val == Integer.MIN_VALUE) { - stream.write(INT_MIN); + private final static int[] DIGITS = new int[1000]; + + static { + for (int i = 0; i < 1000; i++) { + DIGITS[i] = (i < 10 ? (2 << 24) : i < 100 ? (1 << 24) : 0) + + (((i / 100) + '0') << 16) + + ((((i / 10) % 10) + '0') << 8) + + i % 10 + '0'; + } + } + + private static final byte[] MIN_INT = "-2147483648".getBytes(); + + public static final void writeInt(final JsonStream stream, int value) throws IOException { + stream.ensure(12); + byte[] buf = stream.buf; + int pos = stream.count; + if (value < 0) { + if (value == Integer.MIN_VALUE) { + System.arraycopy(MIN_INT, 0, buf, pos, MIN_INT.length); + stream.count = pos + MIN_INT.length; + return; + } + value = -value; + buf[pos++] = '-'; + } + final int q1 = value / 1000; + if (q1 == 0) { + pos += writeFirstBuf(buf, DIGITS[value], pos); + stream.count = pos; return; } - if (val < 0) { - stream.write('-'); - val = -val; + final int r1 = value - q1 * 1000; + final int q2 = q1 / 1000; + if (q2 == 0) { + final int v1 = DIGITS[r1]; + final int v2 = DIGITS[q1]; + int off = writeFirstBuf(buf, v2, pos); + writeBuf(buf, v1, pos + off); + stream.count = pos + 3 + off; + return; } - if (stream.buf.length - stream.count < 10) { - stream.flushBuffer(); - } - int charPos = stream.count + stringSize(val); - stream.count = charPos; - int q, r; - // Generate two digits per iteration - while (val >= 65536) { - q = val / 100; - // really: r = i - (q * 100); - r = val - ((q << 6) + (q << 5) + (q << 2)); - val = q; - stream.buf[--charPos] = DigitOnes[r]; - stream.buf[--charPos] = DigitTens[r]; - } - - // Fall thru to fast mode for smaller numbers - // assert(i <= 65536, i); - for (; ; ) { - q = (val * 52429) >>> (16 + 3); - r = val - ((q << 3) + (q << 1)); // r = i-(q*10) ... - stream.buf[--charPos] = digits[r]; - val = q; - if (val == 0) break; + final int r2 = q1 - q2 * 1000; + final long q3 = q2 / 1000; + final int v1 = DIGITS[r1]; + final int v2 = DIGITS[r2]; + if (q3 == 0) { + pos += writeFirstBuf(buf, DIGITS[q2], pos); + } else { + final int r3 = (int) (q2 - q3 * 1000); + buf[pos++] = (byte) (q3 + '0'); + writeBuf(buf, DIGITS[r3], pos); + pos += 3; } + writeBuf(buf, v2, pos); + writeBuf(buf, v1, pos + 3); + stream.count = pos + 6; } - private final static int[] sizeTable = {9, 99, 999, 9999, 99999, 999999, 9999999, - 99999999, 999999999, Integer.MAX_VALUE}; + private static int writeFirstBuf(final byte[] buf, final int v, int pos) { + final int start = v >> 24; + if (start == 0) { + buf[pos++] = (byte) (v >> 16); + buf[pos++] = (byte) (v >> 8); + } else if (start == 1) { + buf[pos++] = (byte) (v >> 8); + } + buf[pos] = (byte) v; + return 3 - start; + } - // Requires positive x - private static int stringSize(int x) { - for (int i = 0; ; i++) - if (x <= sizeTable[i]) - return i + 1; + private static void writeBuf(final byte[] buf, final int v, int pos) { + buf[pos] = (byte) (v >> 16); + buf[pos + 1] = (byte) (v >> 8); + buf[pos + 2] = (byte) v; } - public static final void writeLong(JsonStream stream, long val) throws IOException { - if (val == Long.MIN_VALUE) { - stream.write(LONG_MIN); + private static final byte[] MIN_LONG = "-9223372036854775808".getBytes(); + + public static final void writeLong(final JsonStream stream, long value) throws IOException { + stream.ensure(22); + byte[] buf = stream.buf; + int pos = stream.count; + if (value < 0) { + if (value == Long.MIN_VALUE) { + System.arraycopy(MIN_LONG, 0, buf, pos, MIN_LONG.length); + stream.count = pos + MIN_LONG.length; + return; + } + value = -value; + buf[pos++] = '-'; + } + final long q1 = value / 1000; + if (q1 == 0) { + pos += writeFirstBuf(buf, DIGITS[(int) value], pos); + stream.count = pos; return; } - if (val < 0) { - stream.write('-'); - val = -val; + final int r1 = (int) (value - q1 * 1000); + final long q2 = q1 / 1000; + if (q2 == 0) { + final int v1 = DIGITS[r1]; + final int v2 = DIGITS[(int) q1]; + int off = writeFirstBuf(buf, v2, pos); + writeBuf(buf, v1, pos + off); + stream.count = pos + 3 + off; + return; } - if (stream.buf.length - stream.count < 20) { - stream.flushBuffer(); - } - long q; - int r; - int charPos = stream.count + stringSize(val); - stream.count = charPos; - char sign = 0; - - // Get 2 digits/iteration using longs until quotient fits into an int - while (val > Integer.MAX_VALUE) { - q = val / 100; - // really: r = i - (q * 100); - r = (int)(val - ((q << 6) + (q << 5) + (q << 2))); - val = q; - stream.buf[--charPos] = DigitOnes[r]; - stream.buf[--charPos] = DigitTens[r]; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)val; - while (i2 >= 65536) { - q2 = i2 / 100; - // really: r = i2 - (q * 100); - r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2)); - i2 = q2; - stream.buf[--charPos] = DigitOnes[r]; - stream.buf[--charPos] = DigitTens[r]; - } - - // Fall thru to fast mode for smaller numbers - // assert(i2 <= 65536, i2); - for (;;) { - q2 = (i2 * 52429) >>> (16+3); - r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ... - stream.buf[--charPos] = digits[r]; - i2 = q2; - if (i2 == 0) break; + final int r2 = (int) (q1 - q2 * 1000); + final long q3 = q2 / 1000; + if (q3 == 0) { + final int v1 = DIGITS[r1]; + final int v2 = DIGITS[r2]; + final int v3 = DIGITS[(int) q2]; + pos += writeFirstBuf(buf, v3, pos); + writeBuf(buf, v2, pos); + writeBuf(buf, v1, pos + 3); + stream.count = pos + 6; + return; } - } - - private static int stringSize(long x) { - long p = 10; - for (int i=1; i<19; i++) { - if (x < p) - return i; - p = 10*p; + final int r3 = (int) (q2 - q3 * 1000); + final int q4 = (int) (q3 / 1000); + if (q4 == 0) { + final int v1 = DIGITS[r1]; + final int v2 = DIGITS[r2]; + final int v3 = DIGITS[r3]; + final int v4 = DIGITS[(int) q3]; + pos += writeFirstBuf(buf, v4, pos); + writeBuf(buf, v3, pos); + writeBuf(buf, v2, pos + 3); + writeBuf(buf, v1, pos + 6); + stream.count = pos + 9; + return; + } + final int r4 = (int) (q3 - q4 * 1000); + final int q5 = q4 / 1000; + if (q5 == 0) { + final int v1 = DIGITS[r1]; + final int v2 = DIGITS[r2]; + final int v3 = DIGITS[r3]; + final int v4 = DIGITS[r4]; + final int v5 = DIGITS[q4]; + pos += writeFirstBuf(buf, v5, pos); + writeBuf(buf, v4, pos); + writeBuf(buf, v3, pos + 3); + writeBuf(buf, v2, pos + 6); + writeBuf(buf, v1, pos + 9); + stream.count = pos + 12; + return; } - return 19; + final int r5 = q4 - q5 * 1000; + final int q6 = q5 / 1000; + final int v1 = DIGITS[r1]; + final int v2 = DIGITS[r2]; + final int v3 = DIGITS[r3]; + final int v4 = DIGITS[r4]; + final int v5 = DIGITS[r5]; + if (q6 == 0) { + pos += writeFirstBuf(buf, DIGITS[q5], pos); + } else { + final int r6 = q5 - q6 * 1000; + buf[pos++] = (byte) (q6 + '0'); + writeBuf(buf, DIGITS[r6], pos); + pos += 3; + } + writeBuf(buf, v5, pos); + writeBuf(buf, v4, pos + 3); + writeBuf(buf, v3, pos + 6); + writeBuf(buf, v2, pos + 9); + writeBuf(buf, v1, pos + 12); + stream.count = pos + 15; } private static final int POW10[] = {1, 10, 100, 1000, 10000, 100000, 1000000}; public static final void writeFloat(JsonStream stream, float val) throws IOException { if (val < 0) { + if (val == Float.NEGATIVE_INFINITY) { + stream.writeVal("-Infinity"); + return; + } stream.write('-'); val = -val; } + if (val > 0x4ffffff) { + if (val == Float.POSITIVE_INFINITY) { + stream.writeVal("Infinity"); + return; + } + stream.writeRaw(Float.toString(val)); + return; + } int precision = 6; int exp = 1000000; // 6 - long lval = (long)val; - stream.writeVal(lval); - long fval = (long)((val - lval) * exp); + long lval = (long)(val * exp + 0.5); + stream.writeVal(lval / exp); + long fval = lval % exp; if (fval == 0) { return; } stream.write('.'); - if (stream.buf.length - stream.count < 10) { - stream.flushBuffer(); - } + stream.ensure(11); for (int p = precision - 1; p > 0 && fval < POW10[p]; p--) { stream.buf[stream.count++] = '0'; } @@ -181,25 +248,31 @@ public static final void writeFloat(JsonStream stream, float val) throws IOExcep public static final void writeDouble(JsonStream stream, double val) throws IOException { if (val < 0) { + if (val == Double.NEGATIVE_INFINITY) { + stream.writeVal("-Infinity"); + return; + } val = -val; stream.write('-'); } if (val > 0x4ffffff) { + if (val == Double.POSITIVE_INFINITY) { + stream.writeVal("Infinity"); + return; + } stream.writeRaw(Double.toString(val)); return; } int precision = 6; int exp = 1000000; // 6 - long lval = (long)val; - stream.writeVal(lval); - long fval = (long)((val - lval) * exp); + long lval = (long)(val * exp + 0.5); + stream.writeVal(lval / exp); + long fval = lval % exp; if (fval == 0) { return; } stream.write('.'); - if (stream.buf.length - stream.count < 10) { - stream.flushBuffer(); - } + stream.ensure(11); for (int p = precision - 1; p > 0 && fval < POW10[p]; p--) { stream.buf[stream.count++] = '0'; } diff --git a/src/main/java/com/jsoniter/output/StreamImplString.java b/src/main/java/com/jsoniter/output/StreamImplString.java index 9b1bd437..7c4a27a7 100644 --- a/src/main/java/com/jsoniter/output/StreamImplString.java +++ b/src/main/java/com/jsoniter/output/StreamImplString.java @@ -1,5 +1,38 @@ +/* +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.output; +import com.jsoniter.spi.JsonException; + import java.io.IOException; class StreamImplString { @@ -7,91 +40,198 @@ class StreamImplString { private static final byte[] ITOA = new byte[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + private static final boolean[] CAN_DIRECT_WRITE = new boolean[128]; + 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; + + static { + for (int i = 0; i < CAN_DIRECT_WRITE.length; i++) { + if (i > 31 && i <= 126 && i != '"' && i != '\\') { + CAN_DIRECT_WRITE[i] = true; + } + } + } - public static final void writeString(JsonStream stream, String val) throws IOException { + public static final void writeString(final JsonStream stream, final String val) throws IOException { int i = 0; int valLen = val.length(); + int toWriteLen = valLen; + int bufLengthMinusTwo = stream.buf.length - 2; // make room for the quotes + if (stream.count + toWriteLen > bufLengthMinusTwo) { + toWriteLen = bufLengthMinusTwo - stream.count; + } + if (toWriteLen < 0) { + stream.ensure(32); + if (stream.count + toWriteLen > bufLengthMinusTwo) { + toWriteLen = bufLengthMinusTwo - stream.count; + } + } + int n = stream.count; + stream.buf[n++] = '"'; // write string, the fast path, without utf8 and escape support - for (; i < valLen && stream.count < stream.buf.length; i++) { + for (; i < toWriteLen; i++) { char c = val.charAt(i); - if (c > 125 || c < 32) { + try { + if (CAN_DIRECT_WRITE[c]) { + stream.buf[n++] = (byte) c; + } else { + break; + } + } catch (ArrayIndexOutOfBoundsException e) { break; } - switch (c) { - case '"': - case '\\': - case '/': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - break; - default: - stream.buf[stream.count++] = (byte) c; - continue; + } + if (i == valLen) { + stream.buf[n++] = '"'; + stream.count = n; + return; + } + stream.count = n; + // for the remaining parts, we process them char by char + writeStringSlowPath(stream, val, i, valLen); + stream.write('"'); + } + + public static final void writeStringWithoutQuote(final JsonStream stream, final String val) throws IOException { + int i = 0; + int valLen = val.length(); + int toWriteLen = valLen; + int bufLen = stream.buf.length; + if (stream.count + toWriteLen > bufLen) { + toWriteLen = bufLen - stream.count; + } + if (toWriteLen < 0) { + stream.ensure(32); + if (stream.count + toWriteLen > bufLen) { + toWriteLen = bufLen - stream.count; + } + } + int n = stream.count; + // write string, the fast path, without utf8 and escape support + for (; i < toWriteLen; i++) { + char c = val.charAt(i); + if (c > 31 && c != '"' && c != '\\' && c < 126) { + stream.buf[n++] = (byte) c; + } else { + break; } - break; } if (i == valLen) { + stream.count = n; return; } + stream.count = n; // for the remaining parts, we process them char by char writeStringSlowPath(stream, val, i, valLen); } private static void writeStringSlowPath(JsonStream stream, String val, int i, int valLen) throws IOException { + boolean escapeUnicode = stream.currentConfig().escapeUnicode(); + if (escapeUnicode) { + for (; i < valLen; i++) { + int c = val.charAt(i); + if (c > 127) { + writeAsSlashU(stream, c); + } else { + writeAsciiChar(stream, c); + } + } + } else { + writeStringSlowPathWithoutEscapeUnicode(stream, val, i, valLen); + } + } + + private static void writeStringSlowPathWithoutEscapeUnicode(JsonStream stream, String val, int i, int valLen) throws IOException { + int _surrogate; for (; i < valLen; i++) { int c = val.charAt(i); - if (c > 125 || c < 32) { - stream.write('\\'); - stream.write('u'); - byte b4 = (byte) (c & 0xf); - byte b3 = (byte) (c >> 4 & 0xf); - byte b2 = (byte) (c >> 8 & 0xf); - byte b1 = (byte) (c >> 12 & 0xf); - stream.write(ITOA[b1]); - stream.write(ITOA[b2]); - stream.write(ITOA[b3]); - stream.write(ITOA[b4]); - } else { - switch (c) { - case '"': - stream.write('\\'); - stream.write('"'); - break; - case '\\': - stream.write('\\'); - stream.write('\\'); - break; - case '/': - stream.write('\\'); - stream.write('/'); - break; - case '\b': - stream.write('\\'); - stream.write('b'); - break; - case '\f': - stream.write('\\'); - stream.write('f'); - break; - case '\n': - stream.write('\\'); - stream.write('n'); - break; - case '\r': - stream.write('\\'); - stream.write('r'); + if (c > 127) { + 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 >= valLen) { // unless we hit the end? break; - case '\t': - stream.write('\\'); - stream.write('t'); - break; - default: - stream.write(c); + } + 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)) + ); } + } else { + writeAsciiChar(stream, c); } } } + + private static void writeAsciiChar(JsonStream stream, int c) throws IOException { + switch (c) { + case '"': + stream.write((byte) '\\', (byte) '"'); + break; + case '\\': + stream.write((byte) '\\', (byte) '\\'); + break; + case '\b': + stream.write((byte) '\\', (byte) 'b'); + break; + case '\f': + stream.write((byte) '\\', (byte) 'f'); + break; + case '\n': + stream.write((byte) '\\', (byte) 'n'); + break; + case '\r': + stream.write((byte) '\\', (byte) 'r'); + break; + case '\t': + stream.write((byte) '\\', (byte) 't'); + break; + default: + if (c < 32) { + writeAsSlashU(stream, c); + } else { + stream.write(c); + } + } + } + + private static void writeAsSlashU(JsonStream stream, int c) throws IOException { + byte b4 = (byte) (c & 0xf); + byte b3 = (byte) (c >> 4 & 0xf); + byte b2 = (byte) (c >> 8 & 0xf); + byte b1 = (byte) (c >> 12 & 0xf); + stream.write((byte) '\\', (byte) 'u', ITOA[b1], ITOA[b2], ITOA[b3], ITOA[b4]); + } } diff --git a/src/main/java/com/jsoniter/spi/Binding.java b/src/main/java/com/jsoniter/spi/Binding.java index 61f604f0..72afc79d 100644 --- a/src/main/java/com/jsoniter/spi/Binding.java +++ b/src/main/java/com/jsoniter/spi/Binding.java @@ -23,6 +23,9 @@ public class Binding { public Encoder encoder; public boolean asMissingWhenNotPresent; public boolean asExtraWhenPresent; + public boolean isNullable = true; + public boolean isCollectionValueNullable = true; + public OmitValue defaultValueToOmit; // then this property will not be unknown // but we do not want to bind it anywhere public boolean shouldSkip; @@ -30,9 +33,9 @@ public class Binding { public int idx; public long mask; - public Binding(Class clazz, Map lookup, Type valueType) { - this.clazz = clazz; - this.clazzTypeLiteral = TypeLiteral.create(clazz); + public Binding(ClassInfo classInfo, Map lookup, Type valueType) { + this.clazz = classInfo.clazz; + this.clazzTypeLiteral = TypeLiteral.create(classInfo.type); this.valueType = substituteTypeVariables(lookup, valueType); this.valueTypeLiteral = TypeLiteral.create(this.valueType); } @@ -55,11 +58,12 @@ private static Type substituteTypeVariables(Map lookup, Type type) for (int i = 0; i < args.length; i++) { args[i] = substituteTypeVariables(lookup, args[i]); } - return new ParameterizedTypeImpl(args, pType.getOwnerType(), pType.getRawType()); + return GenericsHelper.createParameterizedType(args, pType.getOwnerType(), pType.getRawType()); } if (type instanceof GenericArrayType) { GenericArrayType gaType = (GenericArrayType) type; - return new GenericArrayTypeImpl(substituteTypeVariables(lookup, gaType.getGenericComponentType())); + Type componentType = substituteTypeVariables(lookup, gaType.getGenericComponentType()); + return GenericsHelper.createGenericArrayType(componentType); } return type; } @@ -103,12 +107,14 @@ public boolean equals(Object o) { Binding binding = (Binding) o; if (clazz != null ? !clazz.equals(binding.clazz) : binding.clazz != null) return false; + if (method != null ? !method.equals(binding.method) : binding.method != null) return false; return name != null ? name.equals(binding.name) : binding.name == null; } @Override public int hashCode() { int result = clazz != null ? clazz.hashCode() : 0; + result = 31 * result + (method != null ? method.hashCode() : 0); result = 31 * result + (name != null ? name.hashCode() : 0); return result; } diff --git a/src/main/java/com/jsoniter/spi/ClassDescriptor.java b/src/main/java/com/jsoniter/spi/ClassDescriptor.java index 90f26f4e..a47dbe5d 100644 --- a/src/main/java/com/jsoniter/spi/ClassDescriptor.java +++ b/src/main/java/com/jsoniter/spi/ClassDescriptor.java @@ -1,25 +1,404 @@ package com.jsoniter.spi; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.lang.reflect.*; +import java.util.*; + +import static java.lang.reflect.Modifier.isTransient; public class ClassDescriptor { + public ClassInfo classInfo; public Class clazz; public Map lookup; public ConstructorDescriptor ctor; public List fields; public List setters; public List getters; - public List wrappers; - public List unwrappers; + public List bindingTypeWrappers; + public List keyValueTypeWrappers; + public List unwrappers; public boolean asExtraForUnknownProperties; public Binding onMissingProperties; public Binding onExtraProperties; + private ClassDescriptor() { + } + + public static ClassDescriptor getDecodingClassDescriptor(ClassInfo classInfo, boolean includingPrivate) { + Class clazz = classInfo.clazz; + Map lookup = collectTypeVariableLookup(classInfo.type); + ClassDescriptor desc = new ClassDescriptor(); + desc.classInfo = classInfo; + desc.clazz = clazz; + desc.lookup = lookup; + desc.ctor = getCtor(clazz); + desc.setters = getSetters(lookup, classInfo, includingPrivate); + desc.getters = new ArrayList(); + desc.fields = getFields(lookup, classInfo, includingPrivate); + desc.bindingTypeWrappers = new ArrayList(); + desc.keyValueTypeWrappers = new ArrayList(); + desc.unwrappers = new ArrayList(); + for (Extension extension : JsoniterSpi.getExtensions()) { + extension.updateClassDescriptor(desc); + } + for (Binding field : desc.fields) { + if (field.valueType instanceof Class) { + Class valueClazz = (Class) field.valueType; + if (valueClazz.isArray()) { + field.valueCanReuse = false; + continue; + } + } + field.valueCanReuse = field.valueTypeLiteral.nativeType == null; + } + decodingDeduplicate(desc); + if (includingPrivate) { + if (desc.ctor.ctor != null) { + desc.ctor.ctor.setAccessible(true); + } + if (desc.ctor.staticFactory != null) { + desc.ctor.staticFactory.setAccessible(true); + } + for (WrapperDescriptor setter : desc.bindingTypeWrappers) { + setter.method.setAccessible(true); + } + } + for (Binding binding : desc.allDecoderBindings()) { + if (binding.fromNames == null) { + binding.fromNames = new String[]{binding.name}; + } + if (binding.field != null && includingPrivate) { + binding.field.setAccessible(true); + } + if (binding.method != null && includingPrivate) { + binding.method.setAccessible(true); + } + if (binding.decoder != null) { + JsoniterSpi.addNewDecoder(binding.decoderCacheKey(), binding.decoder); + } + } + return desc; + } + + public static ClassDescriptor getEncodingClassDescriptor(ClassInfo classInfo, boolean includingPrivate) { + Class clazz = classInfo.clazz; + Map lookup = collectTypeVariableLookup(classInfo.type); + ClassDescriptor desc = new ClassDescriptor(); + desc.classInfo = classInfo; + desc.clazz = clazz; + desc.lookup = lookup; + desc.fields = getFields(lookup, classInfo, includingPrivate); + desc.getters = getGetters(lookup, classInfo, includingPrivate); + desc.bindingTypeWrappers = new ArrayList(); + desc.keyValueTypeWrappers = new ArrayList(); + desc.unwrappers = new ArrayList(); + for (Extension extension : JsoniterSpi.getExtensions()) { + extension.updateClassDescriptor(desc); + } + encodingDeduplicate(desc); + for (Binding binding : desc.allEncoderBindings()) { + if (binding.toNames == null) { + binding.toNames = new String[]{binding.name}; + } + if (binding.field != null && includingPrivate) { + binding.field.setAccessible(true); + } + if (binding.method != null && includingPrivate) { + binding.method.setAccessible(true); + } + if (binding.encoder != null) { + JsoniterSpi.addNewEncoder(binding.encoderCacheKey(), binding.encoder); + } + } + return desc; + } + + private static void decodingDeduplicate(ClassDescriptor desc) { + HashMap byFromName = new HashMap(); + HashMap byFieldName = new HashMap(); + for (Binding field : desc.fields) { + for (String fromName : field.fromNames) { + if (byFromName.containsKey(fromName)) { + throw new JsonException("field decode from same name: " + fromName); + } + byFromName.put(fromName, field); + } + byFieldName.put(field.name, field); + } + ArrayList iteratingSetters = new ArrayList(desc.setters); + Collections.reverse(iteratingSetters); + for (Binding setter : iteratingSetters) { + if (setter.fromNames.length == 0) { + continue; + } + Binding existing = byFieldName.get(setter.name); + if (existing != null) { + existing.fromNames = new String[0]; + } + deduplicateByFromName(byFromName, setter); + } + for (WrapperDescriptor wrapper : desc.bindingTypeWrappers) { + for (Binding param : wrapper.parameters) { + deduplicateByFromName(byFromName, param); + } + } + for (Binding param : desc.ctor.parameters) { + deduplicateByFromName(byFromName, param); + } + } + + private static void deduplicateByFromName(Map byFromName, Binding setter) { + for (String fromName : setter.fromNames) { + Binding existing = byFromName.get(fromName); + if (existing == null) { + byFromName.put(fromName, setter); + continue; + } + existing.fromNames = new String[0]; + } + } + + private static void encodingDeduplicate(ClassDescriptor desc) { + HashMap byToName = new HashMap(); + HashMap byFieldName = new HashMap(); + for (Binding field : desc.fields) { + for (String toName : field.toNames) { + if (byToName.containsKey(toName)) { + throw new JsonException("field encode to same name: " + toName); + } + byToName.put(toName, field); + } + byFieldName.put(field.name, field); + } + for (Binding getter : new ArrayList(desc.getters)) { + if (getter.toNames.length == 0) { + continue; + } + Binding existing = byFieldName.get(getter.name); + if (existing != null) { + existing.toNames = new String[0]; + } + for (String toName : getter.toNames) { + existing = byToName.get(toName); + if (existing == null) { + byToName.put(toName, getter); + continue; + } + existing.toNames = new String[0]; + } + } + } + + private static ConstructorDescriptor getCtor(Class clazz) { + ConstructorDescriptor cctor = new ConstructorDescriptor(); + if (JsoniterSpi.canCreate(clazz)) { + cctor.objectFactory = JsoniterSpi.getObjectFactory(clazz); + return cctor; + } + try { + cctor.ctor = clazz.getDeclaredConstructor(); + } catch (Exception e) { + cctor.ctor = null; + } + return cctor; + } + + private static List getFields(Map lookup, ClassInfo classInfo, boolean includingPrivate) { + ArrayList bindings = new ArrayList(); + for (Field field : getAllFields(classInfo.clazz)) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + if (includingPrivate) { + field.setAccessible(true); + } + if (isTransient(field.getModifiers())) { + continue; + } + Binding binding = createBindingFromField(lookup, classInfo, field); + if (!includingPrivate && !Modifier.isPublic(field.getModifiers())) { + binding.toNames = new String[0]; + binding.fromNames = new String[0]; + } + if (!includingPrivate && !Modifier.isPublic(field.getType().getModifiers())) { + binding.toNames = new String[0]; + binding.fromNames = new String[0]; + } + bindings.add(binding); + } + return bindings; + } + + private static Binding createBindingFromField(Map lookup, ClassInfo classInfo, Field field) { + try { + Binding binding = new Binding(classInfo, lookup, field.getGenericType()); + binding.fromNames = new String[]{field.getName()}; + binding.toNames = new String[]{field.getName()}; + binding.name = field.getName(); + binding.annotations = field.getAnnotations(); + binding.field = field; + return binding; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new JsonException("failed to create binding for field: " + field, e); + } + } + + private static List getAllFields(Class clazz) { + ArrayList allFields = new ArrayList(); + Class current = clazz; + while (current != null) { + allFields.addAll(Arrays.asList(current.getDeclaredFields())); + current = current.getSuperclass(); + } + return allFields; + } + + private static List getSetters(Map lookup, ClassInfo classInfo, boolean includingPrivate) { + ArrayList setters = new ArrayList(); + for (Method method : getAllMethods(classInfo.clazz, includingPrivate)) { + if (Modifier.isStatic(method.getModifiers())) { + continue; + } + String methodName = method.getName(); + if (methodName.length() < 4) { + continue; + } + if (!methodName.startsWith("set")) { + continue; + } + Type[] paramTypes = method.getGenericParameterTypes(); + if (paramTypes.length != 1) { + continue; + } + if (!includingPrivate && !Modifier.isPublic(method.getParameterTypes()[0].getModifiers())) { + continue; + } + if (includingPrivate) { + method.setAccessible(true); + } + try { + String fromName = translateSetterName(methodName); + Field field = null; + try { + field = method.getDeclaringClass().getDeclaredField(fromName); + } catch (NoSuchFieldException e) { + // ignore + } + Binding setter = new Binding(classInfo, lookup, paramTypes[0]); + if (field != null && isTransient(field.getModifiers())) { + setter.fromNames = new String[0]; + } else { + setter.fromNames = new String[]{fromName}; + } + setter.name = fromName; + setter.method = method; + setter.annotations = method.getAnnotations(); + setters.add(setter); + } catch (JsonException e) { + throw e; + } catch (Exception e) { + throw new JsonException("failed to create binding from setter: " + method, e); + } + } + return setters; + } + + private static List getAllMethods(Class clazz, boolean includingPrivate) { + List allMethods = Arrays.asList(clazz.getMethods()); + if (includingPrivate) { + allMethods = new ArrayList(); + Class current = clazz; + while (current != null) { + allMethods.addAll(Arrays.asList(current.getDeclaredMethods())); + current = current.getSuperclass(); + } + } + return allMethods; + } + + private static String translateSetterName(String methodName) { + if (!methodName.startsWith("set")) { + return null; + } + String fromName = methodName.substring("set".length()); + char[] fromNameChars = fromName.toCharArray(); + fromNameChars[0] = Character.toLowerCase(fromNameChars[0]); + fromName = new String(fromNameChars); + return fromName; + } + + private static List getGetters(Map lookup, ClassInfo classInfo, boolean includingPrivate) { + ArrayList getters = new ArrayList(); + for (Method method : getAllMethods(classInfo.clazz, includingPrivate)) { + if (Modifier.isStatic(method.getModifiers())) { + continue; + } + String methodName = method.getName(); + if ("getClass".equals(methodName)) { + continue; + } + if (methodName.length() < 4) { + continue; + } + if (!methodName.startsWith("get")) { + continue; + } + if (method.getGenericParameterTypes().length != 0) { + continue; + } + String toName = methodName.substring("get".length()); + char[] toNameChars = toName.toCharArray(); + toNameChars[0] = Character.toLowerCase(toNameChars[0]); + toName = new String(toNameChars); + Binding getter = new Binding(classInfo, lookup, method.getGenericReturnType()); + Field field = null; + try { + field = method.getDeclaringClass().getDeclaredField(toName); + } catch (NoSuchFieldException e) { + // ignore + } + if (field != null && isTransient(field.getModifiers())) { + getter.toNames = new String[0]; + } else { + getter.toNames = new String[]{toName}; + } + getter.name = toName; + getter.method = method; + getter.annotations = method.getAnnotations(); + getters.add(getter); + } + return getters; + } + + private static Map collectTypeVariableLookup(Type type) { + HashMap vars = new HashMap(); + if (null == type) { + return vars; + } + if (type instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) type; + Type[] actualTypeArguments = pType.getActualTypeArguments(); + Class clazz = (Class) pType.getRawType(); + for (int i = 0; i < clazz.getTypeParameters().length; i++) { + TypeVariable variable = clazz.getTypeParameters()[i]; + vars.put(variable.getName() + "@" + clazz.getCanonicalName(), actualTypeArguments[i]); + } + vars.putAll(collectTypeVariableLookup(clazz.getGenericSuperclass())); + return vars; + } + if (type instanceof Class) { + Class clazz = (Class) type; + vars.putAll(collectTypeVariableLookup(clazz.getGenericSuperclass())); + return vars; + } + if (type instanceof WildcardType) { + return vars; + } + throw new JsonException("unexpected type: " + type); + } + public List allBindings() { ArrayList bindings = new ArrayList(8); bindings.addAll(fields); @@ -32,8 +411,8 @@ public List allBindings() { if (ctor != null) { bindings.addAll(ctor.parameters); } - if (wrappers != null) { - for (WrapperDescriptor setter : wrappers) { + if (bindingTypeWrappers != null) { + for (WrapperDescriptor setter : bindingTypeWrappers) { bindings.addAll(setter.parameters); } } @@ -47,16 +426,46 @@ public List allDecoderBindings() { if (ctor != null) { bindings.addAll(ctor.parameters); } - for (WrapperDescriptor setter : wrappers) { + for (WrapperDescriptor setter : bindingTypeWrappers) { bindings.addAll(setter.parameters); } return bindings; } + public List allEncoderBindings() { ArrayList bindings = new ArrayList(8); bindings.addAll(fields); bindings.addAll(getters); return bindings; } + + public List encodeTos() { + HashMap previousAppearance = new HashMap(); + ArrayList encodeTos = new ArrayList(8); + collectEncodeTo(encodeTos, fields, previousAppearance); + collectEncodeTo(encodeTos, getters, previousAppearance); + ArrayList removedNulls = new ArrayList(encodeTos.size()); + for (EncodeTo encodeTo : encodeTos) { + if (encodeTo != null) { + removedNulls.add(encodeTo); + } + } + return removedNulls; + } + + private void collectEncodeTo(ArrayList encodeTos, List fields, HashMap previousAppearance) { + for (Binding field : fields) { + for (String toName : field.toNames) { + if (previousAppearance.containsKey(toName)) { + encodeTos.set(previousAppearance.get(toName), null); + } + previousAppearance.put(toName, encodeTos.size()); + EncodeTo encodeTo = new EncodeTo(); + encodeTo.binding = field; + encodeTo.toName = toName; + encodeTos.add(encodeTo); + } + } + } } diff --git a/src/main/java/com/jsoniter/spi/ClassInfo.java b/src/main/java/com/jsoniter/spi/ClassInfo.java new file mode 100644 index 00000000..e85f7e38 --- /dev/null +++ b/src/main/java/com/jsoniter/spi/ClassInfo.java @@ -0,0 +1,27 @@ +package com.jsoniter.spi; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; + +public class ClassInfo { + + public final Type type; + public final Class clazz; + public final Type[] typeArgs; + + public ClassInfo(Type type) { + this.type = type; + if (type instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) type; + clazz = (Class) pType.getRawType(); + typeArgs = pType.getActualTypeArguments(); + } else if (type instanceof WildcardType) { + clazz = Object.class; + typeArgs = new Type[0]; + } else { + clazz = (Class) type; + typeArgs = new Type[0]; + } + } +} diff --git a/src/main/java/com/jsoniter/spi/Config.java b/src/main/java/com/jsoniter/spi/Config.java new file mode 100644 index 00000000..5b0d6c98 --- /dev/null +++ b/src/main/java/com/jsoniter/spi/Config.java @@ -0,0 +1,555 @@ +package com.jsoniter.spi; + +import com.jsoniter.annotation.*; +import com.jsoniter.output.EncodingMode; + +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.util.*; + +public class Config extends EmptyExtension { + + private final String configName; + private final Builder builder; + private static volatile Map configs = new HashMap(); + private volatile Map decoderCacheKeys = new HashMap(); + private volatile Map encoderCacheKeys = new HashMap(); + private final static Map primitiveOmitValues = new HashMap() {{ + put(boolean.class, new OmitValue.False()); + put(char.class, new OmitValue.ZeroChar()); + put(byte.class, new OmitValue.ZeroByte()); + put(short.class, new OmitValue.ZeroShort()); + put(int.class, new OmitValue.ZeroInt()); + put(long.class, new OmitValue.ZeroLong()); + put(float.class, new OmitValue.ZeroFloat()); + put(double.class, new OmitValue.ZeroDouble()); + }}; + + protected Config(String configName, Builder builder) { + this.configName = configName; + this.builder = builder; + } + + public String configName() { + return configName; + } + + public String getDecoderCacheKey(Type type) { + String cacheKey = decoderCacheKeys.get(type); + if (cacheKey != null) { + return cacheKey; + } + synchronized (this) { + cacheKey = decoderCacheKeys.get(type); + if (cacheKey != null) { + return cacheKey; + } + cacheKey = TypeLiteral.create(type).getDecoderCacheKey(configName); + HashMap newCache = new HashMap(decoderCacheKeys); + newCache.put(type, cacheKey); + decoderCacheKeys = newCache; + return cacheKey; + } + } + + public String getEncoderCacheKey(Type type) { + String cacheKey = encoderCacheKeys.get(type); + if (cacheKey != null) { + return cacheKey; + } + synchronized (this) { + cacheKey = encoderCacheKeys.get(type); + if (cacheKey != null) { + return cacheKey; + } + cacheKey = TypeLiteral.create(type).getEncoderCacheKey(configName); + HashMap newCache = new HashMap(encoderCacheKeys); + newCache.put(type, cacheKey); + encoderCacheKeys = newCache; + return cacheKey; + } + } + + public DecodingMode decodingMode() { + return builder.decodingMode; + } + + protected Builder builder() { + return builder; + } + + public Builder copyBuilder() { + return builder.copy(); + } + + public int indentionStep() { + return builder.indentionStep; + } + + public boolean omitDefaultValue() { + return builder.omitDefaultValue; + } + + public boolean escapeUnicode() { + return builder.escapeUnicode; + } + + public EncodingMode encodingMode() { + return builder.encodingMode; + } + + public static class Builder { + + private DecodingMode decodingMode; + private EncodingMode encodingMode; + private int indentionStep; + private boolean escapeUnicode = true; + private boolean omitDefaultValue = false; + + public Builder() { + String envMode = System.getenv("JSONITER_DECODING_MODE"); + if (envMode != null) { + decodingMode = DecodingMode.valueOf(envMode); + } else { + decodingMode = DecodingMode.REFLECTION_MODE; + } + envMode = System.getenv("JSONITER_ENCODING_MODE"); + if (envMode != null) { + encodingMode = EncodingMode.valueOf(envMode); + } else { + encodingMode = EncodingMode.REFLECTION_MODE; + } + } + + public Builder decodingMode(DecodingMode decodingMode) { + this.decodingMode = decodingMode; + return this; + } + + public Builder encodingMode(EncodingMode encodingMode) { + this.encodingMode = encodingMode; + return this; + } + + public Builder indentionStep(int indentionStep) { + this.indentionStep = indentionStep; + return this; + } + + public Builder omitDefaultValue(boolean omitDefaultValue) { + this.omitDefaultValue = omitDefaultValue; + return this; + } + + public Builder escapeUnicode(boolean escapeUnicode) { + this.escapeUnicode = escapeUnicode; + return this; + } + + public Config build() { + String configName = JsoniterSpi.assignConfigName(this); + Config config = configs.get(configName); + if (config != null) { + return config; + } + synchronized (Config.class) { + config = configs.get(configName); + if (config != null) { + return config; + } + config = doBuild(configName); + HashMap newCache = new HashMap(configs); + newCache.put(configName, config); + configs = newCache; + return config; + } + } + + protected Config doBuild(String configName) { + return new Config(configName, this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Builder builder = (Builder) o; + + if (indentionStep != builder.indentionStep) return false; + if (escapeUnicode != builder.escapeUnicode) return false; + if (decodingMode != builder.decodingMode) return false; + if (omitDefaultValue != builder.omitDefaultValue) return false; + return encodingMode == builder.encodingMode; + } + + @Override + public int hashCode() { + int result = decodingMode != null ? decodingMode.hashCode() : 0; + result = 31 * result + (encodingMode != null ? encodingMode.hashCode() : 0); + result = 31 * result + indentionStep; + result = 31 * result + (escapeUnicode ? 1 : 0); + result = 31 * result + (omitDefaultValue ? 1 : 0); + return result; + } + + public Builder copy() { + Builder builder = new Builder(); + builder.encodingMode = encodingMode; + builder.decodingMode = decodingMode; + builder.indentionStep = indentionStep; + builder.escapeUnicode = escapeUnicode; + builder.omitDefaultValue = omitDefaultValue; + return builder; + } + + @Override + public String toString() { + return "Config{" + + "decodingMode=" + decodingMode + + ", encodingMode=" + encodingMode + + ", indentionStep=" + indentionStep + + ", escapeUnicode=" + escapeUnicode + + ", omitDefaultValue=" + omitDefaultValue + + '}'; + } + } + + public static final Config INSTANCE = new Builder().build(); + + @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.classInfo, desc.lookup, Object.class); + binding.name = fieldName; + binding.fromNames = new String[]{binding.name}; + binding.toNames = new String[0]; + binding.shouldSkip = true; + desc.fields.add(binding); + } + for (String fieldName : jsonObject.unknownPropertiesBlacklist()) { + Binding binding = new Binding(desc.classInfo, desc.lookup, Object.class); + binding.name = fieldName; + binding.fromNames = new String[]{binding.name}; + binding.toNames = new String[0]; + 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 (getJsonUnwrapper(method.getAnnotations()) == null) { + continue; + } + desc.unwrappers.add(new UnwrapperDescriptor(method)); + } + } + + private void detectWrappers(ClassDescriptor desc, List allMethods) { + for (Method method : allMethods) { + if (Modifier.isStatic(method.getModifiers())) { + continue; + } + JsonWrapper jsonWrapper = getJsonWrapper(method.getAnnotations()); + if (jsonWrapper == null) { + continue; + } + Annotation[][] annotations = method.getParameterAnnotations(); + String[] paramNames = getParamNames(method, annotations.length); + Iterator iter = desc.setters.iterator(); + while(iter.hasNext()) { + if (method.equals(iter.next().method)) { + iter.remove(); + } + } + if (JsonWrapperType.BINDING.equals(jsonWrapper.value())) { + WrapperDescriptor wrapper = new WrapperDescriptor(); + wrapper.method = method; + for (int i = 0; i < annotations.length; i++) { + Annotation[] paramAnnotations = annotations[i]; + Binding binding = new Binding(desc.classInfo, desc.lookup, method.getGenericParameterTypes()[i]); + JsonProperty jsonProperty = getJsonProperty(paramAnnotations); + if (jsonProperty != null) { + updateBindingWithJsonProperty(binding, jsonProperty); + } + if (binding.name == null || binding.name.length() == 0) { + binding.name = paramNames[i]; + } + binding.fromNames = new String[]{binding.name}; + binding.toNames = new String[]{binding.name}; + binding.annotations = paramAnnotations; + wrapper.parameters.add(binding); + } + desc.bindingTypeWrappers.add(wrapper); + } else if (JsonWrapperType.KEY_VALUE.equals(jsonWrapper.value())) { + desc.keyValueTypeWrappers.add(method); + } else { + throw new JsonException("unknown json wrapper type: " + jsonWrapper.value()); + } + } + } + + 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.classInfo, desc.lookup, method.getGenericParameterTypes()[i]); + if (jsonProperty != null) { + updateBindingWithJsonProperty(binding, jsonProperty); + } + if (binding.name == null || binding.name.length() == 0) { + binding.name = paramNames[i]; + } + binding.fromNames = new String[]{binding.name}; + binding.toNames = new String[]{binding.name}; + binding.annotations = paramAnnotations; + desc.ctor.parameters.add(binding); + } + } + } + + private void detectCtor(ClassDescriptor desc) { + if (desc.ctor == null) { + return; + } + 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.classInfo, desc.lookup, ctor.getGenericParameterTypes()[i]); + if (jsonProperty != null) { + updateBindingWithJsonProperty(binding, jsonProperty); + } + if (binding.name == null || binding.name.length() == 0) { + binding.name = paramNames[i]; + } + binding.fromNames = new String[]{binding.name}; + binding.toNames = new String[]{binding.name}; + binding.annotations = paramAnnotations; + desc.ctor.parameters.add(binding); + } + } + } + + private void updateBindings(ClassDescriptor desc) { + boolean globalOmitDefault = JsoniterSpi.getCurrentConfig().omitDefaultValue(); + for (Binding binding : desc.allBindings()) { + boolean annotated = false; + JsonIgnore jsonIgnore = getJsonIgnore(binding.annotations); + if (jsonIgnore != null) { + annotated = true; + if (jsonIgnore.ignoreDecoding()) { + binding.fromNames = new String[0]; + } + if (jsonIgnore.ignoreEncoding()) { + binding.toNames = new String[0]; + } + } + // map JsonUnwrapper is not getter + JsonUnwrapper jsonUnwrapper = getJsonUnwrapper(binding.annotations); + if (jsonUnwrapper != null) { + annotated = true; + binding.fromNames = new String[0]; + binding.toNames = new String[0]; + } + if (globalOmitDefault) { + binding.defaultValueToOmit = createOmitValue(binding.valueType); + } + JsonProperty jsonProperty = getJsonProperty(binding.annotations); + if (jsonProperty != null) { + annotated = true; + updateBindingWithJsonProperty(binding, jsonProperty); + } + if (getAnnotation(binding.annotations, JsonMissingProperties.class) != null) { + annotated = true; + // 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) { + annotated = true; + // 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; + } + if (annotated && binding.field != null) { + if (desc.setters != null) { + for (Binding setter : desc.setters) { + if (binding.field.getName().equals(setter.name)) { + setter.fromNames = new String[0]; + setter.toNames = new String[0]; + } + } + } + if (desc.getters != null) { + for (Binding getter : desc.getters) { + if (binding.field.getName().equals(getter.name)) { + getter.fromNames = new String[0]; + getter.toNames = new String[0]; + } + } + } + } + } + } + + private void updateBindingWithJsonProperty(Binding binding, JsonProperty jsonProperty) { + binding.asMissingWhenNotPresent = jsonProperty.required(); + binding.isNullable = jsonProperty.nullable(); + binding.isCollectionValueNullable = jsonProperty.collectionValueNullable(); + String defaultValueToOmit = jsonProperty.defaultValueToOmit(); + if (!defaultValueToOmit.isEmpty()) { + binding.defaultValueToOmit = OmitValue.Parsed.parse(binding.valueType, defaultValueToOmit); + } + String altName = jsonProperty.value(); + if (!altName.isEmpty()) { + if (binding.name == null) { + binding.name = altName; + } + binding.fromNames = new String[]{altName}; + binding.toNames = new String[]{altName}; + } + if (jsonProperty.from().length > 0) { + binding.fromNames = jsonProperty.from(); + } + if (jsonProperty.to().length > 0) { + binding.toNames = jsonProperty.to(); + } + Class decoderClass = jsonProperty.decoder(); + if (decoderClass != Decoder.class) { + try { + try { + Constructor decoderCtor = decoderClass.getConstructor(Binding.class); + binding.decoder = (Decoder) decoderCtor.newInstance(binding); + } catch (NoSuchMethodException e) { + binding.decoder = (Decoder) decoderClass.newInstance(); + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new JsonException(e); + } + } + Class encoderClass = jsonProperty.encoder(); + if (encoderClass != Encoder.class) { + try { + try { + Constructor encoderCtor = encoderClass.getConstructor(Binding.class); + binding.encoder = (Encoder) encoderCtor.newInstance(binding); + } catch (NoSuchMethodException e) { + binding.encoder = (Encoder) encoderClass.newInstance(); + } + } catch (JsonException e) { + throw e; + } catch (Exception e) { + throw new JsonException(e); + } + } + if (jsonProperty.implementation() != Object.class) { + binding.valueType = GenericsHelper.useImpl(binding.valueType, jsonProperty.implementation()); + binding.valueTypeLiteral = TypeLiteral.create(binding.valueType); + } + } + + protected OmitValue createOmitValue(Type valueType) { + OmitValue omitValue = primitiveOmitValues.get(valueType); + if (omitValue != null) { + return omitValue; + } + return new OmitValue.Null(); + } + + protected JsonWrapper getJsonWrapper(Annotation[] annotations) { + return getAnnotation(annotations, JsonWrapper.class); + } + + protected JsonUnwrapper getJsonUnwrapper(Annotation[] annotations) { + return getAnnotation(annotations, JsonUnwrapper.class); + } + + 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/spi/Decoder.java b/src/main/java/com/jsoniter/spi/Decoder.java index d0077f68..1d69101f 100644 --- a/src/main/java/com/jsoniter/spi/Decoder.java +++ b/src/main/java/com/jsoniter/spi/Decoder.java @@ -1,6 +1,5 @@ package com.jsoniter.spi; -import com.jsoniter.CodegenAccess; import com.jsoniter.JsonIterator; import java.io.IOException; @@ -33,41 +32,6 @@ public Object decode(JsonIterator iter) throws IOException { public abstract short decodeShort(JsonIterator iter) throws IOException; } - class StringShortDecoder extends 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; - } - } - - class MaybeStringShortDecoder extends 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; - } - } - abstract class IntDecoder implements Decoder { @Override public Object decode(JsonIterator iter) throws IOException { @@ -77,41 +41,6 @@ public Object decode(JsonIterator iter) throws IOException { public abstract int decodeInt(JsonIterator iter) throws IOException; } - class StringIntDecoder extends 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; - } - } - - class MaybeStringIntDecoder extends 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; - } - } - abstract class LongDecoder implements Decoder { @Override public Object decode(JsonIterator iter) throws IOException { @@ -121,41 +50,6 @@ public Object decode(JsonIterator iter) throws IOException { public abstract long decodeLong(JsonIterator iter) throws IOException; } - class StringLongDecoder extends 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; - } - } - - class MaybeStringLongDecoder extends 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; - } - } - abstract class FloatDecoder implements Decoder { @Override public Object decode(JsonIterator iter) throws IOException { @@ -165,41 +59,6 @@ public Object decode(JsonIterator iter) throws IOException { public abstract float decodeFloat(JsonIterator iter) throws IOException; } - class StringFloatDecoder extends 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; - } - } - - class MaybeStringFloatDecoder extends 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; - } - } - abstract class DoubleDecoder implements Decoder { @Override @@ -210,38 +69,4 @@ public Object decode(JsonIterator iter) throws IOException { public abstract double decodeDouble(JsonIterator iter) throws IOException; } - class StringDoubleDecoder extends 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; - } - } - - class MaybeStringDoubleDecoder extends 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/DecodingMode.java b/src/main/java/com/jsoniter/spi/DecodingMode.java similarity index 94% rename from src/main/java/com/jsoniter/DecodingMode.java rename to src/main/java/com/jsoniter/spi/DecodingMode.java index e8873f3a..8e42f774 100644 --- a/src/main/java/com/jsoniter/DecodingMode.java +++ b/src/main/java/com/jsoniter/spi/DecodingMode.java @@ -1,4 +1,4 @@ -package com.jsoniter; +package com.jsoniter.spi; public enum DecodingMode { /** diff --git a/src/main/java/com/jsoniter/spi/EmptyEncoder.java b/src/main/java/com/jsoniter/spi/EmptyEncoder.java deleted file mode 100644 index a4744b08..00000000 --- a/src/main/java/com/jsoniter/spi/EmptyEncoder.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.jsoniter.spi; - -import com.jsoniter.any.Any; -import com.jsoniter.output.JsonStream; - -import java.io.IOException; - -public class EmptyEncoder implements Encoder { - - @Override - public void encode(Object obj, JsonStream stream) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public Any wrap(Object obj) { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/com/jsoniter/spi/EncodeTo.java b/src/main/java/com/jsoniter/spi/EncodeTo.java new file mode 100644 index 00000000..7c03a34e --- /dev/null +++ b/src/main/java/com/jsoniter/spi/EncodeTo.java @@ -0,0 +1,6 @@ +package com.jsoniter.spi; + +public class EncodeTo { + public Binding binding; + public String toName; +} diff --git a/src/main/java/com/jsoniter/spi/Encoder.java b/src/main/java/com/jsoniter/spi/Encoder.java index d9d249e4..f359061c 100644 --- a/src/main/java/com/jsoniter/spi/Encoder.java +++ b/src/main/java/com/jsoniter/spi/Encoder.java @@ -9,7 +9,11 @@ public interface Encoder { void encode(Object obj, JsonStream stream) throws IOException; - Any wrap(Object obj); + + interface ReflectionEncoder extends Encoder { + + Any wrap(Object obj); + } abstract class BooleanEncoder implements Encoder { @Override @@ -20,7 +24,7 @@ public void encode(Object obj, JsonStream stream) throws IOException { public abstract void encodeBoolean(boolean obj, JsonStream stream) throws IOException; } - abstract class ShortEncoder implements Encoder { + abstract class ShortEncoder implements ReflectionEncoder { @Override public void encode(Object obj, JsonStream stream) throws IOException { @@ -46,7 +50,7 @@ public void encodeShort(short obj, JsonStream stream) throws IOException { } } - abstract class IntEncoder implements Encoder { + abstract class IntEncoder implements ReflectionEncoder { @Override public void encode(Object obj, JsonStream stream) throws IOException { encodeInt((Integer) obj, stream); @@ -71,7 +75,7 @@ public void encodeInt(int obj, JsonStream stream) throws IOException { } } - abstract class LongEncoder implements Encoder { + abstract class LongEncoder implements ReflectionEncoder { @Override public void encode(Object obj, JsonStream stream) throws IOException { encodeLong((Long) obj, stream); @@ -96,7 +100,7 @@ public void encodeLong(long obj, JsonStream stream) throws IOException { } } - abstract class FloatEncoder implements Encoder { + abstract class FloatEncoder implements ReflectionEncoder { @Override public void encode(Object obj, JsonStream stream) throws IOException { encodeFloat((Float) obj, stream); @@ -121,7 +125,7 @@ public void encodeFloat(float obj, JsonStream stream) throws IOException { } } - abstract class DoubleEncoder implements Encoder { + abstract class DoubleEncoder implements ReflectionEncoder { @Override public void encode(Object obj, JsonStream stream) throws IOException { encodeDouble((Double) obj, stream); diff --git a/src/main/java/com/jsoniter/spi/GenericArrayTypeImpl.java b/src/main/java/com/jsoniter/spi/GenericArrayTypeImpl.java deleted file mode 100644 index 18a9e8fb..00000000 --- a/src/main/java/com/jsoniter/spi/GenericArrayTypeImpl.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jsoniter.spi; - -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.Type; - -public class GenericArrayTypeImpl implements GenericArrayType { - - private final Type componentType; - - GenericArrayTypeImpl(Type componentType) { - this.componentType = componentType; - } - - @Override - public Type getGenericComponentType() { - return componentType; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GenericArrayTypeImpl that = (GenericArrayTypeImpl) o; - - return componentType != null ? componentType.equals(that.componentType) : that.componentType == null; - - } - - @Override - public int hashCode() { - return componentType != null ? componentType.hashCode() : 0; - } - - @Override - public String toString() { - return "GenericArrayTypeImpl{" + - "componentType=" + componentType + - '}'; - } -} diff --git a/src/main/java/com/jsoniter/spi/GenericsHelper.java b/src/main/java/com/jsoniter/spi/GenericsHelper.java new file mode 100644 index 00000000..bebd5b4c --- /dev/null +++ b/src/main/java/com/jsoniter/spi/GenericsHelper.java @@ -0,0 +1,136 @@ +package com.jsoniter.spi; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; + +public class GenericsHelper { + + public static GenericArrayType createGenericArrayType(Type componentType) { + return new GenericArrayTypeImpl(componentType); + } + + public static ParameterizedType createParameterizedType(Type[] actualTypeArguments, Type ownerType, Type rawType) { + return new ParameterizedTypeImpl(actualTypeArguments, ownerType, rawType); + } + + public static boolean isSameClass(Type type, Class clazz) { + if (type == clazz) { + return true; + } + if (type instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) type; + return pType.getRawType() == clazz; + } + return false; + } + + public static Type useImpl(Type type, Class clazz) { + if (type instanceof Class) { + return clazz; + } + if (type instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) type; + return createParameterizedType(pType.getActualTypeArguments(), pType.getOwnerType(), clazz); + } + throw new JsonException("can not change impl for: " + type); + } + + private static class GenericArrayTypeImpl implements GenericArrayType { + + private final Type componentType; + + GenericArrayTypeImpl(Type componentType) { + this.componentType = componentType; + } + + @Override + public Type getGenericComponentType() { + return componentType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GenericArrayTypeImpl that = (GenericArrayTypeImpl) o; + + return componentType != null ? componentType.equals(that.componentType) : that.componentType == null; + + } + + @Override + public int hashCode() { + return componentType != null ? componentType.hashCode() : 0; + } + + @Override + public String toString() { + return "GenericArrayTypeImpl{" + + "componentType=" + componentType + + '}'; + } + } + + private static class ParameterizedTypeImpl implements ParameterizedType { + private final Type[] actualTypeArguments; + private final Type ownerType; + private final Type rawType; + + public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){ + this.actualTypeArguments = actualTypeArguments; + this.ownerType = ownerType; + this.rawType = rawType; + } + + public Type[] getActualTypeArguments() { + return actualTypeArguments; + } + + public Type getOwnerType() { + return ownerType; + } + + public Type getRawType() { + return rawType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ParameterizedTypeImpl that = (ParameterizedTypeImpl) o; + + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(actualTypeArguments, that.actualTypeArguments)) return false; + if (ownerType != null ? !ownerType.equals(that.ownerType) : that.ownerType != null) return false; + return rawType != null ? rawType.equals(that.rawType) : that.rawType == null; + + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(actualTypeArguments); + result = 31 * result + (ownerType != null ? ownerType.hashCode() : 0); + result = 31 * result + (rawType != null ? rawType.hashCode() : 0); + return result; + } + + @Override + public String toString() { + String rawTypeName = rawType.toString(); + if (rawType instanceof Class) { + Class clazz = (Class) rawType; + rawTypeName = clazz.getName(); + } + return "ParameterizedTypeImpl{" + + "actualTypeArguments=" + Arrays.toString(actualTypeArguments) + + ", ownerType=" + ownerType + + ", rawType=" + rawTypeName + + '}'; + } + } +} diff --git a/src/main/java/com/jsoniter/JsonException.java b/src/main/java/com/jsoniter/spi/JsonException.java similarity index 61% rename from src/main/java/com/jsoniter/JsonException.java rename to src/main/java/com/jsoniter/spi/JsonException.java index 7e3df13e..dc156f2b 100644 --- a/src/main/java/com/jsoniter/JsonException.java +++ b/src/main/java/com/jsoniter/spi/JsonException.java @@ -1,4 +1,4 @@ -package com.jsoniter; +package com.jsoniter.spi; public class JsonException extends RuntimeException { public JsonException() { @@ -15,8 +15,4 @@ public JsonException(String message, Throwable cause) { public JsonException(Throwable cause) { super(cause); } - - public JsonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } } diff --git a/src/main/java/com/jsoniter/spi/JsoniterSpi.java b/src/main/java/com/jsoniter/spi/JsoniterSpi.java index 612b6f4e..4b40e77e 100644 --- a/src/main/java/com/jsoniter/spi/JsoniterSpi.java +++ b/src/main/java/com/jsoniter/spi/JsoniterSpi.java @@ -1,24 +1,112 @@ package com.jsoniter.spi; -import com.jsoniter.JsonException; - -import java.lang.reflect.*; -import java.util.*; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class JsoniterSpi { - static List extensions = new ArrayList(); - static Map typeImpls = new HashMap(); - static volatile Map encoders = new HashMap(); - static volatile Map decoders = new HashMap(); - static volatile Map objectFactories = new HashMap(); + // registered at startup, global state + private static Config defaultConfig; + private static List extensions = new ArrayList(); + private static Map typeImpls = new HashMap(); + private static Map globalMapKeyDecoders = new HashMap(); + private static Map globalMapKeyEncoders = new HashMap(); + private static Map globalTypeDecoders = new HashMap(); + private static Map globalTypeEncoders = new HashMap(); + private static Map globalPropertyDecoders = new HashMap(); + private static Map globalPropertyEncoders = new HashMap(); + + // current state + private static ThreadLocal currentConfig = new ThreadLocal() { + @Override + protected Config initialValue() { + return defaultConfig; + } + }; + private static volatile Map configNames = new HashMap(); + private static volatile Map mapKeyEncoders = new HashMap(); + private static volatile Map mapKeyDecoders = new HashMap(); + private static volatile Map encoders = new HashMap(); + private static volatile Map decoders = new HashMap(); + private static volatile Map objectFactories = new HashMap(); + + static { + defaultConfig = Config.INSTANCE; + } + + // === global === + + public static void setCurrentConfig(Config val) { + currentConfig.set(val); + } + + // TODO usage of this method leads to potentially unexpected side effects. All usage should be checked. + public static void clearCurrentConfig() { + currentConfig.set(defaultConfig); + } + + public static Config getCurrentConfig() { + return currentConfig.get(); + } + + public static void setDefaultConfig(Config val) { + defaultConfig = val; + } + + public static Config getDefaultConfig() { + return defaultConfig; + } + + public static String assignConfigName(Object obj) { + String configName = configNames.get(obj); + if (configName != null) { + return configName; + } + return assignNewConfigName(obj); + } + + private synchronized static String assignNewConfigName(Object obj) { + String configName = configNames.get(obj); + if (configName != null) { + return configName; + } + + long hash = obj.toString().hashCode(); + if (hash < 0) { + hash = Long.MAX_VALUE + hash; + } + configName = "jsoniter_codegen.cfg" + hash + "."; + copyGlobalSettings(configName); + HashMap newCache = new HashMap(configNames); + newCache.put(obj, configName); + configNames = newCache; + return configName; + } public static void registerExtension(Extension extension) { - extensions.add(extension); + if (!extensions.contains(extension)) { + extensions.add(extension); + } } + // TODO: use composite pattern public static List getExtensions() { - return Collections.unmodifiableList(extensions); + ArrayList combined = new ArrayList(extensions); + combined.add(currentConfig.get()); + return combined; + } + + public static void registerMapKeyDecoder(Type mapKeyType, Decoder mapKeyDecoder) { + globalMapKeyDecoders.put(mapKeyType, mapKeyDecoder); + copyGlobalMapKeyDecoder(getCurrentConfig().configName(), mapKeyType, mapKeyDecoder); + } + + public static void registerMapKeyEncoder(Type mapKeyType, Encoder mapKeyEncoder) { + globalMapKeyEncoders.put(mapKeyType, mapKeyEncoder); + copyGlobalMapKeyEncoder(getCurrentConfig().configName(), mapKeyType, mapKeyEncoder); } public static void registerTypeImplementation(Class superClazz, Class implClazz) { @@ -30,35 +118,123 @@ public static Class getTypeImplementation(Class superClazz) { } public static void registerTypeDecoder(Class clazz, Decoder decoder) { - addNewDecoder(TypeLiteral.create(clazz).getDecoderCacheKey(), decoder); + globalTypeDecoders.put(clazz, decoder); + copyGlobalTypeDecoder(getCurrentConfig().configName(), clazz, decoder); } public static void registerTypeDecoder(TypeLiteral typeLiteral, Decoder decoder) { - addNewDecoder(typeLiteral.getDecoderCacheKey(), decoder); + globalTypeDecoders.put(typeLiteral.getType(), decoder); + copyGlobalTypeDecoder(getCurrentConfig().configName(), typeLiteral.getType(), decoder); } - public static void registerPropertyDecoder(Class clazz, String field, Decoder decoder) { - addNewDecoder(field + "@" + TypeLiteral.create(clazz).getDecoderCacheKey(), decoder); + public static void registerTypeEncoder(Class clazz, Encoder encoder) { + globalTypeEncoders.put(clazz, encoder); + copyGlobalTypeEncoder(getCurrentConfig().configName(), clazz, encoder); } - public static void registerPropertyDecoder(TypeLiteral typeLiteral, String field, Decoder decoder) { - addNewDecoder(field + "@" + typeLiteral.getDecoderCacheKey(), decoder); + public static void registerTypeEncoder(TypeLiteral typeLiteral, Encoder encoder) { + globalTypeEncoders.put(typeLiteral.getType(), encoder); + copyGlobalTypeEncoder(getCurrentConfig().configName(), typeLiteral.getType(), encoder); } - public static void registerTypeEncoder(Class clazz, Encoder encoder) { - addNewEncoder(TypeLiteral.create(clazz).getEncoderCacheKey(), encoder); + public static void registerPropertyDecoder(Class clazz, String property, Decoder decoder) { + globalPropertyDecoders.put(new TypeProperty(clazz, property), decoder); + copyGlobalPropertyDecoder(getCurrentConfig().configName(), clazz, property, decoder); } - public static void registerTypeEncoder(TypeLiteral typeLiteral, Encoder encoder) { - addNewEncoder(typeLiteral.getDecoderCacheKey(), encoder); + public static void registerPropertyDecoder(TypeLiteral typeLiteral, String property, Decoder decoder) { + globalPropertyDecoders.put(new TypeProperty(typeLiteral.getType(), property), decoder); + copyGlobalPropertyDecoder(getCurrentConfig().configName(), typeLiteral.getType(), property, decoder); + } + + public static void registerPropertyEncoder(Class clazz, String property, Encoder encoder) { + globalPropertyEncoders.put(new TypeProperty(clazz, property), encoder); + copyGlobalPropertyEncoder(getCurrentConfig().configName(), clazz, property, encoder); + } + + public static void registerPropertyEncoder(TypeLiteral typeLiteral, String property, Encoder encoder) { + globalPropertyEncoders.put(new TypeProperty(typeLiteral.getType(), property), encoder); + copyGlobalPropertyEncoder(getCurrentConfig().configName(), typeLiteral.getType(), property, encoder); } - public static void registerPropertyEncoder(Class clazz, String field, Encoder encoder) { - addNewEncoder(field + "@" + TypeLiteral.create(clazz).getEncoderCacheKey(), encoder); + // === copy from global to current === + + private static void copyGlobalSettings(String configName) { + for (Map.Entry entry : globalMapKeyDecoders.entrySet()) { + copyGlobalMapKeyDecoder(configName, entry.getKey(), entry.getValue()); + } + for (Map.Entry entry : globalMapKeyEncoders.entrySet()) { + copyGlobalMapKeyEncoder(configName, entry.getKey(), entry.getValue()); + } + for (Map.Entry entry : globalTypeDecoders.entrySet()) { + copyGlobalTypeDecoder(configName, entry.getKey(), entry.getValue()); + } + for (Map.Entry entry : globalTypeEncoders.entrySet()) { + copyGlobalTypeEncoder(configName, entry.getKey(), entry.getValue()); + } + for (Map.Entry entry : globalPropertyDecoders.entrySet()) { + copyGlobalPropertyDecoder(configName, entry.getKey().type, entry.getKey().property, entry.getValue()); + } + for (Map.Entry entry : globalPropertyEncoders.entrySet()) { + copyGlobalPropertyEncoder(configName, entry.getKey().type, entry.getKey().property, entry.getValue()); + + } + } + + private static void copyGlobalPropertyEncoder(String configName, Type type, String property, Encoder propertyEncoder) { + addNewEncoder(property + "@" + TypeLiteral.create(type).getEncoderCacheKey(), propertyEncoder); + } + + private static void copyGlobalPropertyDecoder(String configName, Type type, String property, Decoder propertyDecoder) { + addNewDecoder(property + "@" + TypeLiteral.create(type).getDecoderCacheKey(), propertyDecoder); + } + + private static void copyGlobalTypeEncoder(String configName, Type type, Encoder typeEncoder) { + addNewEncoder(TypeLiteral.create(type).getEncoderCacheKey(configName), typeEncoder); + } + + private static void copyGlobalTypeDecoder(String configName, Type type, Decoder typeDecoder) { + addNewDecoder(TypeLiteral.create(type).getDecoderCacheKey(configName), typeDecoder); + } + + private static void copyGlobalMapKeyDecoder(String configName, Type mapKeyType, Decoder mapKeyDecoder) { + addNewMapDecoder(TypeLiteral.create(mapKeyType).getDecoderCacheKey(configName), mapKeyDecoder); } - public static void registerPropertyEncoder(TypeLiteral typeLiteral, String field, Encoder encoder) { - addNewEncoder(field + "@" + typeLiteral.getDecoderCacheKey(), encoder); + private static void copyGlobalMapKeyEncoder(String configName, Type mapKeyType, Encoder mapKeyEncoder) { + addNewMapEncoder(TypeLiteral.create(mapKeyType).getEncoderCacheKey(configName), mapKeyEncoder); + } + + public static String getMapKeyEncoderCacheKey(Type mapKeyType) { + TypeLiteral typeLiteral = TypeLiteral.create(mapKeyType); + return typeLiteral.getEncoderCacheKey(); + } + + public static String getMapKeyDecoderCacheKey(Type mapKeyType) { + TypeLiteral typeLiteral = TypeLiteral.create(mapKeyType); + return typeLiteral.getDecoderCacheKey(); + } + + // === current === + + public synchronized static void addNewMapDecoder(String cacheKey, Decoder mapKeyDecoder) { + HashMap newCache = new HashMap(mapKeyDecoders); + newCache.put(cacheKey, mapKeyDecoder); + mapKeyDecoders = newCache; + } + + public static Decoder getMapKeyDecoder(String cacheKey) { + return mapKeyDecoders.get(cacheKey); + } + + public synchronized static void addNewMapEncoder(String cacheKey, Encoder mapKeyEncoder) { + HashMap newCache = new HashMap(mapKeyEncoders); + newCache.put(cacheKey, mapKeyEncoder); + mapKeyEncoders = newCache; + } + + public static Encoder getMapKeyEncoder(String cacheKey) { + return mapKeyEncoders.get(cacheKey); } public static Decoder getDecoder(String cacheKey) { @@ -67,7 +243,11 @@ public static Decoder getDecoder(String cacheKey) { public synchronized static void addNewDecoder(String cacheKey, Decoder decoder) { HashMap newCache = new HashMap(decoders); - newCache.put(cacheKey, decoder); + if (decoder == null) { + newCache.remove(cacheKey); + } else { + newCache.put(cacheKey, decoder); + } decoders = newCache; } @@ -77,7 +257,11 @@ public static Encoder getEncoder(String cacheKey) { public synchronized static void addNewEncoder(String cacheKey, Encoder encoder) { HashMap newCache = new HashMap(encoders); - newCache.put(cacheKey, encoder); + if (encoder == null) { + newCache.remove(cacheKey); + } else { + newCache.put(cacheKey, encoder); + } encoders = newCache; } @@ -85,7 +269,7 @@ public static boolean canCreate(Class clazz) { if (objectFactories.containsKey(clazz)) { return true; } - for (Extension extension : extensions) { + for (Extension extension : getExtensions()) { if (extension.canCreate(clazz)) { addObjectFactory(clazz, extension); return true; @@ -95,7 +279,11 @@ public static boolean canCreate(Class clazz) { } public static Object create(Class clazz) { - return objectFactories.get(clazz).create(clazz); + return getObjectFactory(clazz).create(clazz); + } + + public static Extension getObjectFactory(Class clazz) { + return objectFactories.get(clazz); } private synchronized static void addObjectFactory(Class clazz, Extension extension) { @@ -104,336 +292,32 @@ private synchronized static void addObjectFactory(Class clazz, Extension extensi objectFactories = copy; } - public static ClassDescriptor getDecodingClassDescriptor(Class clazz, boolean includingPrivate) { - Map lookup = collectTypeVariableLookup(clazz); - ClassDescriptor desc = new ClassDescriptor(); - desc.clazz = clazz; - desc.lookup = lookup; - desc.ctor = getCtor(clazz); - desc.fields = getFields(lookup, clazz, includingPrivate); - desc.setters = getSetters(lookup, clazz, includingPrivate); - desc.wrappers = new ArrayList(); - desc.unwrappers = new ArrayList(); - for (Extension extension : extensions) { - extension.updateClassDescriptor(desc); - } - for (Binding field : desc.fields) { - if (field.valueType instanceof Class) { - Class valueClazz = (Class) field.valueType; - if (valueClazz.isArray()) { - field.valueCanReuse = false; - continue; - } - } - field.valueCanReuse = field.valueTypeLiteral.nativeType == null; - } - decodingDeduplicate(desc); - if (includingPrivate) { - if (desc.ctor.ctor != null) { - desc.ctor.ctor.setAccessible(true); - } - if (desc.ctor.staticFactory != null) { - desc.ctor.staticFactory.setAccessible(true); - } - for (WrapperDescriptor setter : desc.wrappers) { - setter.method.setAccessible(true); - } - } - for (Binding binding : desc.allDecoderBindings()) { - if (binding.fromNames == null) { - binding.fromNames = new String[]{binding.name}; - } - if (binding.field != null && includingPrivate) { - binding.field.setAccessible(true); - } - if (binding.method != null && includingPrivate) { - binding.method.setAccessible(true); - } - if (binding.decoder != null) { - JsoniterSpi.addNewDecoder(binding.decoderCacheKey(), binding.decoder); - } - } - return desc; - } - - public static ClassDescriptor getEncodingClassDescriptor(Class clazz, boolean includingPrivate) { - Map lookup = collectTypeVariableLookup(clazz); - ClassDescriptor desc = new ClassDescriptor(); - desc.clazz = clazz; - desc.lookup = lookup; - desc.fields = getFields(lookup, clazz, includingPrivate); - desc.getters = getGetters(lookup, clazz, includingPrivate); - desc.wrappers = new ArrayList(); - desc.unwrappers = new ArrayList(); - for (Extension extension : extensions) { - extension.updateClassDescriptor(desc); - } - encodingDeduplicate(desc); - for (Binding binding : desc.allEncoderBindings()) { - if (binding.toNames == null) { - binding.toNames = new String[]{binding.name}; - } - if (binding.field != null && includingPrivate) { - binding.field.setAccessible(true); - } - if (binding.method != null && includingPrivate) { - binding.method.setAccessible(true); - } - if (binding.encoder != null) { - JsoniterSpi.addNewEncoder(binding.encoderCacheKey(), binding.encoder); - } - } - return desc; - } + private static class TypeProperty { - private static void decodingDeduplicate(ClassDescriptor desc) { - HashMap byName = new HashMap(); - for (Binding field : desc.fields) { - if (byName.containsKey(field.name)) { - throw new JsonException("field name conflict: " + field.name); - } - byName.put(field.name, field); - } - for (Binding setter : desc.setters) { - Binding existing = byName.get(setter.name); - if (existing == null) { - byName.put(setter.name, setter); - continue; - } - if (desc.fields.remove(existing)) { - continue; - } - throw new JsonException("setter name conflict: " + setter.name); - } - for (WrapperDescriptor wrapper : desc.wrappers) { - for (Binding param : wrapper.parameters) { - Binding existing = byName.get(param.name); - if (existing == null) { - byName.put(param.name, param); - continue; - } - if (desc.fields.remove(existing)) { - continue; - } - if (desc.setters.remove(existing)) { - continue; - } - throw new JsonException("wrapper parameter name conflict: " + param.name); - } - } - for (Binding param : desc.ctor.parameters) { - Binding existing = byName.get(param.name); - if (existing == null) { - byName.put(param.name, param); - continue; - } - if (desc.fields.remove(existing)) { - continue; - } - if (desc.setters.remove(existing)) { - continue; - } - throw new JsonException("ctor parameter name conflict: " + param.name); - } - } + public final Type type; + public final String property; - private static void encodingDeduplicate(ClassDescriptor desc) { - HashMap byName = new HashMap(); - for (Binding field : desc.fields) { - if (byName.containsKey(field.name)) { - throw new JsonException("field name conflict: " + field.name); - } - byName.put(field.name, field); + private TypeProperty(Type type, String property) { + this.type = type; + this.property = property; } - for (Binding getter : desc.getters) { - Binding existing = byName.get(getter.name); - if (existing == null) { - byName.put(getter.name, getter); - continue; - } - if (desc.fields.remove(existing)) { - continue; - } - throw new JsonException("getter name conflict: " + getter.name); - } - } - private static ConstructorDescriptor getCtor(Class clazz) { - ConstructorDescriptor cctor = new ConstructorDescriptor(); - if (canCreate(clazz)) { - cctor.objectFactory = objectFactories.get(clazz); - return cctor; - } - try { - cctor.ctor = clazz.getDeclaredConstructor(); - } catch (Exception e) { - cctor.ctor = null; - } - return cctor; - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; - private static List getFields(Map lookup, Class clazz, boolean includingPrivate) { - ArrayList bindings = new ArrayList(); - for (Field field : getAllFields(clazz, includingPrivate)) { - if (Modifier.isStatic(field.getModifiers())) { - continue; - } - if (Modifier.isTransient(field.getModifiers())) { - continue; - } - if (includingPrivate) { - field.setAccessible(true); - } - Binding binding = createBindingFromField(lookup, clazz, field); - bindings.add(binding); - } - return bindings; - } - - private static Binding createBindingFromField(Map lookup, Class clazz, Field field) { - try { - Binding binding = new Binding(clazz, lookup, field.getGenericType()); - binding.fromNames = new String[]{field.getName()}; - binding.name = field.getName(); - binding.annotations = field.getAnnotations(); - binding.field = field; - return binding; - } catch (Exception e) { - throw new JsonException("failed to create binding for field: " + field, e); - } - } + TypeProperty that = (TypeProperty) o; - private static List getAllFields(Class clazz, boolean includingPrivate) { - List allFields = Arrays.asList(clazz.getFields()); - if (includingPrivate) { - allFields = new ArrayList(); - Class current = clazz; - while (current != null) { - allFields.addAll(Arrays.asList(current.getDeclaredFields())); - current = current.getSuperclass(); - } - } - return allFields; - } - - private static List getSetters(Map lookup, Class clazz, boolean includingPrivate) { - ArrayList setters = new ArrayList(); - List allMethods = Arrays.asList(clazz.getMethods()); - if (includingPrivate) { - allMethods = new ArrayList(); - Class current = clazz; - while (current != null) { - allMethods.addAll(Arrays.asList(current.getDeclaredMethods())); - current = current.getSuperclass(); - } - } - for (Method method : allMethods) { - if (Modifier.isStatic(method.getModifiers())) { - continue; - } - String methodName = method.getName(); - if (methodName.length() < 4) { - continue; - } - if (!methodName.startsWith("set")) { - continue; - } - Type[] paramTypes = method.getGenericParameterTypes(); - if (paramTypes.length != 1) { - continue; - } - if (includingPrivate) { - method.setAccessible(true); - } - try { - String fromName = translateSetterName(methodName); - Binding binding = new Binding(clazz, lookup, paramTypes[0]); - binding.fromNames = new String[]{fromName}; - binding.name = fromName; - binding.method = method; - binding.annotations = method.getAnnotations(); - setters.add(binding); - } catch (Exception e) { - throw new JsonException("failed to create binding from setter: " + method, e); - } + if (type != null ? !type.equals(that.type) : that.type != null) return false; + return property != null ? property.equals(that.property) : that.property == null; } - return setters; - } - private static String translateSetterName(String methodName) { - if (!methodName.startsWith("set")) { - return null; - } - String fromName = methodName.substring("set".length()); - char[] fromNameChars = fromName.toCharArray(); - fromNameChars[0] = Character.toLowerCase(fromNameChars[0]); - fromName = new String(fromNameChars); - return fromName; - } - - private static List getGetters(Map lookup, Class clazz, boolean includingPrivate) { - ArrayList getters = new ArrayList(); - for (Method method : clazz.getMethods()) { - if (Modifier.isStatic(method.getModifiers())) { - continue; - } - String methodName = method.getName(); - if ("getClass".equals(methodName)) { - continue; - } - if (methodName.length() < 4) { - continue; - } - if (!methodName.startsWith("get")) { - continue; - } - if (method.getGenericParameterTypes().length != 0) { - continue; - } - String toName = methodName.substring("get".length()); - char[] fromNameChars = toName.toCharArray(); - fromNameChars[0] = Character.toLowerCase(fromNameChars[0]); - toName = new String(fromNameChars); - Binding getter = new Binding(clazz, lookup, method.getGenericReturnType()); - getter.toNames = new String[]{toName}; - getter.name = toName; - getter.method = method; - getter.annotations = method.getAnnotations(); - getters.add(getter); - } - return getters; - } - - public static void dump() { - for (String cacheKey : decoders.keySet()) { - System.err.println(cacheKey); - } - for (String cacheKey : encoders.keySet()) { - System.err.println(cacheKey); - } - } - - private static Map collectTypeVariableLookup(Type type) { - HashMap vars = new HashMap(); - if (null == type) { - return vars; - } - if (type instanceof ParameterizedType) { - ParameterizedType pType = (ParameterizedType) type; - Type[] actualTypeArguments = pType.getActualTypeArguments(); - Class clazz = (Class) pType.getRawType(); - for (int i = 0; i < clazz.getTypeParameters().length; i++) { - TypeVariable variable = clazz.getTypeParameters()[i]; - vars.put(variable.getName() + "@" + clazz.getCanonicalName(), actualTypeArguments[i]); - } - vars.putAll(collectTypeVariableLookup(clazz.getGenericSuperclass())); - return vars; - } - if (type instanceof Class) { - Class clazz = (Class) type; - vars.putAll(collectTypeVariableLookup(clazz.getGenericSuperclass())); - return vars; + @Override + public int hashCode() { + int result = type != null ? type.hashCode() : 0; + result = 31 * result + (property != null ? property.hashCode() : 0); + return result; } - throw new JsonException("unexpected type: " + type); } } diff --git a/src/main/java/com/jsoniter/spi/OmitValue.java b/src/main/java/com/jsoniter/spi/OmitValue.java new file mode 100644 index 00000000..c56ae591 --- /dev/null +++ b/src/main/java/com/jsoniter/spi/OmitValue.java @@ -0,0 +1,206 @@ +package com.jsoniter.spi; + +import java.lang.reflect.Type; + +public interface OmitValue { + + boolean shouldOmit(Object val); + + String code(); + + class Null implements OmitValue { + + @Override + public boolean shouldOmit(Object val) { + return val == null; + } + + @Override + public String code() { + return "null == %s"; + } + } + + class ZeroByte implements OmitValue { + + @Override + public boolean shouldOmit(Object val) { + return (Byte) val == 0; + } + + @Override + public String code() { + return "0 == %s"; + } + } + + class ZeroShort implements OmitValue { + + @Override + public boolean shouldOmit(Object val) { + return (Short) val == 0; + } + + @Override + public String code() { + return "0 == %s"; + } + } + + class ZeroInt implements OmitValue { + + @Override + public boolean shouldOmit(Object val) { + return ((Integer) val) == 0; + } + + @Override + public String code() { + return "0 == %s"; + } + } + + class ZeroLong implements OmitValue { + + @Override + public boolean shouldOmit(Object val) { + return ((Long) val) == 0; + } + + @Override + public String code() { + return "0 == %s"; + } + } + + class ZeroFloat implements OmitValue { + + @Override + public boolean shouldOmit(Object val) { + return ((Float) val) == 0; + } + + @Override + public String code() { + return "0 == %s"; + } + } + + class ZeroDouble implements OmitValue { + + @Override + public boolean shouldOmit(Object val) { + return ((Double) val) == 0; + } + + @Override + public String code() { + return "0 == %s"; + } + } + + class ZeroChar implements OmitValue { + + @Override + public boolean shouldOmit(Object val) { + return (Character) val == 0; + } + + @Override + public String code() { + return "0 == %s"; + } + } + + class False implements OmitValue { + + @Override + public boolean shouldOmit(Object val) { + return !((Boolean) val); + } + + @Override + public String code() { + return "false == %s"; + } + } + + class Parsed implements OmitValue { + + private final Object defaultValue; + private final String code; + + public Parsed(Object defaultValue, String code) { + this.defaultValue = defaultValue; + this.code = code; + } + + public static OmitValue parse(Type valueType, String defaultValueToOmit) { + if ("void".equals(defaultValueToOmit)) { + return null; + } else if ("null".equals(defaultValueToOmit)) { + return new OmitValue.Null(); + } else if (boolean.class.equals(valueType)) { + Boolean defaultValue = Boolean.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + " == %s"); + } else if (Boolean.class.equals(valueType)) { + Boolean defaultValue = Boolean.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + " == %s.booleanValue()"); + } else if (int.class.equals(valueType)) { + Integer defaultValue = Integer.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + " == %s"); + } else if (Integer.class.equals(valueType)) { + Integer defaultValue = Integer.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + " == %s.intValue()"); + } else if (byte.class.equals(valueType)) { + Byte defaultValue = Byte.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + " == %s"); + } else if (Byte.class.equals(valueType)) { + Byte defaultValue = Byte.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + " == %s.byteValue()"); + } else if (short.class.equals(valueType)) { + Short defaultValue = Short.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + " == %s"); + } else if (Short.class.equals(valueType)) { + Short defaultValue = Short.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + " == %s.shortValue()"); + } else if (long.class.equals(valueType)) { + Long defaultValue = Long.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + "L == %s"); + } else if (Long.class.equals(valueType)) { + Long defaultValue = Long.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + "L == %s.longValue()"); + } else if (float.class.equals(valueType)) { + Float defaultValue = Float.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + "F == %s"); + } else if (Float.class.equals(valueType)) { + Float defaultValue = Float.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + "F == %s.floatValue()"); + } else if (double.class.equals(valueType)) { + Double defaultValue = Double.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + "D == %s"); + } else if (Double.class.equals(valueType)) { + Double defaultValue = Double.valueOf(defaultValueToOmit); + return new OmitValue.Parsed(defaultValue, defaultValueToOmit + "D == %s.doubleValue()"); + } else if (char.class.equals(valueType) && defaultValueToOmit.length() == 1) { + Character defaultValue = defaultValueToOmit.charAt(0); + return new OmitValue.Parsed(defaultValue, "'" + defaultValueToOmit + "' == %s"); + } else if (Character.class.equals(valueType) && defaultValueToOmit.length() == 1) { + Character defaultValue = defaultValueToOmit.charAt(0); + return new OmitValue.Parsed(defaultValue, "'" + defaultValueToOmit + "' == %s.charValue()"); + } else { + throw new UnsupportedOperationException("failed to parse defaultValueToOmit: " + defaultValueToOmit); + } + } + + @Override + public boolean shouldOmit(Object val) { + return defaultValue.equals(val); + } + + @Override + public String code() { + return code; + } + } +} diff --git a/src/main/java/com/jsoniter/spi/ParameterizedTypeImpl.java b/src/main/java/com/jsoniter/spi/ParameterizedTypeImpl.java deleted file mode 100644 index ffbdfe30..00000000 --- a/src/main/java/com/jsoniter/spi/ParameterizedTypeImpl.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.jsoniter.spi; - -import com.jsoniter.JsonException; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Arrays; - -public class ParameterizedTypeImpl implements ParameterizedType { - private final Type[] actualTypeArguments; - private final Type ownerType; - private final Type rawType; - - public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){ - this.actualTypeArguments = actualTypeArguments; - this.ownerType = ownerType; - this.rawType = rawType; - } - - public Type[] getActualTypeArguments() { - return actualTypeArguments; - } - - public Type getOwnerType() { - return ownerType; - } - - public Type getRawType() { - return rawType; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ParameterizedTypeImpl that = (ParameterizedTypeImpl) o; - - // Probably incorrect - comparing Object[] arrays with Arrays.equals - if (!Arrays.equals(actualTypeArguments, that.actualTypeArguments)) return false; - if (ownerType != null ? !ownerType.equals(that.ownerType) : that.ownerType != null) return false; - return rawType != null ? rawType.equals(that.rawType) : that.rawType == null; - - } - - @Override - public int hashCode() { - int result = Arrays.hashCode(actualTypeArguments); - result = 31 * result + (ownerType != null ? ownerType.hashCode() : 0); - result = 31 * result + (rawType != null ? rawType.hashCode() : 0); - return result; - } - - @Override - public String toString() { - String rawTypeName = rawType.toString(); - if (rawType instanceof Class) { - Class clazz = (Class) rawType; - rawTypeName = clazz.getName(); - } - return "ParameterizedTypeImpl{" + - "actualTypeArguments=" + Arrays.toString(actualTypeArguments) + - ", ownerType=" + ownerType + - ", rawType=" + rawTypeName + - '}'; - } - - public static boolean isSameClass(Type type, Class clazz) { - if (type == clazz) { - return true; - } - if (type instanceof ParameterizedType) { - ParameterizedType pType = (ParameterizedType) type; - return pType.getRawType() == clazz; - } - return false; - } - - public static Type useImpl(Type type, Class clazz) { - if (type instanceof Class) { - return clazz; - } - if (type instanceof ParameterizedType) { - ParameterizedType pType = (ParameterizedType) type; - return new ParameterizedTypeImpl(pType.getActualTypeArguments(), pType.getOwnerType(), clazz); - } - throw new JsonException("can not change impl for: " + type); - } -} diff --git a/src/main/java/com/jsoniter/Slice.java b/src/main/java/com/jsoniter/spi/Slice.java similarity index 98% rename from src/main/java/com/jsoniter/Slice.java rename to src/main/java/com/jsoniter/spi/Slice.java index 8c801e13..0eacf3b0 100644 --- a/src/main/java/com/jsoniter/Slice.java +++ b/src/main/java/com/jsoniter/spi/Slice.java @@ -1,4 +1,4 @@ -package com.jsoniter; +package com.jsoniter.spi; public class Slice { diff --git a/src/main/java/com/jsoniter/spi/TypeLiteral.java b/src/main/java/com/jsoniter/spi/TypeLiteral.java index 5f731eb7..b4461390 100644 --- a/src/main/java/com/jsoniter/spi/TypeLiteral.java +++ b/src/main/java/com/jsoniter/spi/TypeLiteral.java @@ -1,11 +1,11 @@ package com.jsoniter.spi; -import com.jsoniter.JsonException; import com.jsoniter.any.Any; import java.lang.reflect.GenericArrayType; 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; @@ -57,6 +57,7 @@ public enum NativeType { final Type type; final String decoderCacheKey; final String encoderCacheKey; + // TODO: remove native type final NativeType nativeType; /** @@ -110,6 +111,8 @@ private static String generateCacheKey(Type type, String prefix) { decoderClassName.append('_'); decoderClassName.append(typeName); } + } catch (JsonException e) { + throw e; } catch (Exception e) { throw new JsonException("failed to generate cache key for: " + type, e); } @@ -118,6 +121,8 @@ private static String generateCacheKey(Type type, String prefix) { Type compType = gaType.getGenericComponentType(); decoderClassName.append(formatTypeWithoutSpecialCharacter(compType)); decoderClassName.append("_array"); + } else if (type instanceof WildcardType) { + decoderClassName.append(Object.class.getName()); } else { throw new UnsupportedOperationException("do not know how to handle: " + type); } @@ -142,6 +147,9 @@ private static String formatTypeWithoutSpecialCharacter(Type type) { GenericArrayType gaType = (GenericArrayType) type; return formatTypeWithoutSpecialCharacter(gaType.getGenericComponentType()) + "_array"; } + if (type instanceof WildcardType) { + return Object.class.getCanonicalName(); + } throw new JsonException("unsupported type: " + type + ", of class " + type.getClass()); } @@ -181,11 +189,19 @@ public Type getType() { } public String getDecoderCacheKey() { - return decoderCacheKey; + return getDecoderCacheKey(JsoniterSpi.getCurrentConfig().configName()); + } + + public String getDecoderCacheKey(String configName) { + return configName + decoderCacheKey; } public String getEncoderCacheKey() { - return encoderCacheKey; + return getEncoderCacheKey(JsoniterSpi.getCurrentConfig().configName()); + } + + public String getEncoderCacheKey(String configName) { + return configName + encoderCacheKey; } public NativeType getNativeType() { diff --git a/src/main/java/com/jsoniter/spi/UnwrapperDescriptor.java b/src/main/java/com/jsoniter/spi/UnwrapperDescriptor.java new file mode 100644 index 00000000..eb758054 --- /dev/null +++ b/src/main/java/com/jsoniter/spi/UnwrapperDescriptor.java @@ -0,0 +1,55 @@ +package com.jsoniter.spi; + +import com.jsoniter.output.JsonStream; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +public class UnwrapperDescriptor { + + public Method method; + + public boolean isMap; + + public TypeLiteral mapValueTypeLiteral; + + public UnwrapperDescriptor(Method method) { + this.method = method; + if (isMapUnwrapper(method)) { + this.isMap = true; + Type mapType = method.getGenericReturnType(); + mapValueTypeLiteral = TypeLiteral.create(Object.class); + if (mapType instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) mapType; + Type[] typeArgs = pType.getActualTypeArguments(); + if (typeArgs.length == 2) { + mapValueTypeLiteral = TypeLiteral.create(typeArgs[1]); + } + } + } else if (isStreamUnwrapper(method)) { + this.isMap = false; + } else { + throw new JsonException("invalid unwrapper method signature: " + method); + } + } + + private boolean isMapUnwrapper(Method method) { + if (method.getParameterTypes().length != 0) { + return false; + } + return Map.class.isAssignableFrom(method.getReturnType()); + } + + private boolean isStreamUnwrapper(Method method) { + if (method.getReturnType() != void.class) { + return false; + } + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 1) { + return false; + } + return parameterTypes[0] == JsonStream.class; + } +} diff --git a/src/main/java/com/jsoniter/static_codegen/StaticCodegen.java b/src/main/java/com/jsoniter/static_codegen/StaticCodegen.java new file mode 100644 index 00000000..89002585 --- /dev/null +++ b/src/main/java/com/jsoniter/static_codegen/StaticCodegen.java @@ -0,0 +1,41 @@ +package com.jsoniter.static_codegen; + +import com.jsoniter.CodegenAccess; +import com.jsoniter.spi.DecodingMode; +import com.jsoniter.JsonIterator; +import com.jsoniter.output.EncodingMode; +import com.jsoniter.output.JsonStream; +import com.jsoniter.spi.JsonException; + +import java.io.File; + +public class StaticCodegen { + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.out.println("StaticCodegen configClassName [outputDir]"); + System.out.println("configClassName: like a.b.Config, a class defining what to codegen"); + System.out.println("outputDir: if not specified, will write to source directory of configClass"); + return; + } + String configClassName = args[0]; + String configJavaFile = configClassName.replace('.', '/') + ".java"; + String outputDir; + if (args.length > 1) { + outputDir = args[1]; + } else { + if (!new File(configJavaFile).exists()) { + throw new JsonException("must execute static code generator in the java source code directory which contains: " + configJavaFile); + } + outputDir = new File(".").getAbsolutePath(); + } + Class clazz = Class.forName(configClassName); + StaticCodegenConfig config = (StaticCodegenConfig) clazz.newInstance(); + JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); + JsonStream.setMode(EncodingMode.DYNAMIC_MODE); + config.setup(); + CodegenAccess.staticGenDecoders( + config.whatToCodegen(), new CodegenAccess.StaticCodegenTarget(outputDir)); + com.jsoniter.output.CodegenAccess.staticGenEncoders( + config.whatToCodegen(), new com.jsoniter.output.CodegenAccess.StaticCodegenTarget(outputDir)); + } +} diff --git a/src/main/java/com/jsoniter/spi/CodegenConfig.java b/src/main/java/com/jsoniter/static_codegen/StaticCodegenConfig.java similarity index 69% rename from src/main/java/com/jsoniter/spi/CodegenConfig.java rename to src/main/java/com/jsoniter/static_codegen/StaticCodegenConfig.java index 002c2d0b..05690524 100644 --- a/src/main/java/com/jsoniter/spi/CodegenConfig.java +++ b/src/main/java/com/jsoniter/static_codegen/StaticCodegenConfig.java @@ -1,6 +1,8 @@ -package com.jsoniter.spi; +package com.jsoniter.static_codegen; -public interface CodegenConfig { +import com.jsoniter.spi.TypeLiteral; + +public interface StaticCodegenConfig { /** * register decoder/encoder before codegen * register extension before codegen diff --git a/src/test/java/com/jsoniter/AllTests.java b/src/test/java/com/jsoniter/AllTests.java deleted file mode 100644 index c23fc24c..00000000 --- a/src/test/java/com/jsoniter/AllTests.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.jsoniter; - -import com.jsoniter.output.*; -import org.junit.BeforeClass; -import org.junit.experimental.categories.Categories; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -public class AllTests { - - public interface StreamingCategory { - } - - @RunWith(Suite.class) - @Suite.SuiteClasses({TestAnnotation.class, TestArray.class, TestCustomizeType.class, TestDemo.class, - TestExisting.class, TestGenerics.class, TestGenerics.class, TestIO.class, TestNested.class, - TestObject.class, TestReadAny.class, TestReflection.class, TestSkip.class, TestSlice.class, - TestString.class, TestWhatIsNext.class, com.jsoniter.output.TestAnnotation.class, - TestAny.class, com.jsoniter.output.TestArray.class, TestCustomizeField.class, com.jsoniter.output.TestCustomizeType.class, - TestMap.class, TestNative.class, TestNested.class, TestObject.class}) - public static class AllTestCases { - } - - @RunWith(Categories.class) - @Categories.ExcludeCategory(StreamingCategory.class) - @Suite.SuiteClasses({AllTestCases.class}) - public static class NonStreamingTests { - - } - - @RunWith(Categories.class) - @Categories.ExcludeCategory(StreamingCategory.class) - @Suite.SuiteClasses({AllTestCases.class}) - public static class NonStreamingTests4Hash { - @BeforeClass - public static void setup() { - JsonStream.setMode(EncodingMode.DYNAMIC_MODE); - JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); - } - } - - @RunWith(Categories.class) - @Categories.ExcludeCategory(StreamingCategory.class) - @Suite.SuiteClasses({AllTestCases.class}) - public static class NonStreamingTests4Strict { - @BeforeClass - public static void setup() { - JsonStream.setMode(EncodingMode.DYNAMIC_MODE); - JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY); - } - } - - @RunWith(Categories.class) - @Categories.IncludeCategory(StreamingCategory.class) - @Suite.SuiteClasses({AllTestCases.class}) - public static class StreamingTests { - - } -} diff --git a/src/test/java/com/jsoniter/BenchGson.java b/src/test/java/com/jsoniter/BenchGson.java new file mode 100644 index 00000000..67747b6c --- /dev/null +++ b/src/test/java/com/jsoniter/BenchGson.java @@ -0,0 +1,295 @@ +package com.jsoniter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; +import com.jsoniter.extra.GsonCompatibilityMode; +import com.jsoniter.spi.DecodingMode; +import com.jsoniter.spi.JsoniterSpi; +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.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Date; +import java.util.List; + +@State(Scope.Thread) +public class BenchGson { + private GsonCompatibilityMode gsonCompatibilityMode; + private Gson gson; + + @Setup(Level.Trial) + public void benchSetup(BenchmarkParams params) { + gson = new GsonBuilder() + .setDateFormat("EEE MMM dd HH:mm:ss Z yyyy") + .create(); + gsonCompatibilityMode = new GsonCompatibilityMode.Builder().setDateFormat("EEE MMM dd HH:mm:ss Z yyyy").build(); + JsoniterSpi.setCurrentConfig(gsonCompatibilityMode); + JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); + if (params != null) { + if (params.getBenchmark().contains("jsoniterDynamicCodegenDecoder")) { + JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); + } + } + } + + @Benchmark + public void gsonDecoder(Blackhole bh) throws IOException { + FileInputStream stream = new FileInputStream("./src/test/tweets.json"); + InputStreamReader reader = new InputStreamReader(stream); + try { + bh.consume(gson.fromJson(reader, new TypeReference>() { + }.getType())); + } finally { + reader.close(); + stream.close(); + } + } + + @Benchmark + public void jsoniterReflectionDecoder(Blackhole bh) throws IOException { + FileInputStream stream = new FileInputStream("./src/test/tweets.json"); + JsonIterator iter = JsonIteratorPool.borrowJsonIterator(); + try { + iter.reset(stream); + bh.consume(iter.read(new TypeReference>() { + }.getType())); + } finally { + JsonIteratorPool.returnJsonIterator(iter); + stream.close(); + } + } + +// +// @Benchmark +// public void jsoniterDynamicCodegenDecoder(Blackhole bh) throws IOException { +// bh.consume(JsonIterator.deserialize(gsonCompatibilityMode, json, BagOfPrimitives.class)); +// } + + public static void main(String[] args) throws Exception { + Main.main(new String[]{ + "BenchGson", + "-i", "5", + "-wi", "5", + "-f", "1", + }); + } + + public static class Tweet { + @JsonProperty + String coordinates; + @JsonProperty + boolean favorited; + @JsonProperty + Date created_at; + @JsonProperty + boolean truncated; + @JsonProperty + Tweet retweeted_status; + @JsonProperty + String id_str; + @JsonProperty + String in_reply_to_id_str; + @JsonProperty + String contributors; + @JsonProperty + String text; + @JsonProperty + long id; + @JsonProperty + String retweet_count; + @JsonProperty + String in_reply_to_status_id_str; + @JsonProperty + Object geo; + @JsonProperty + boolean retweeted; + @JsonProperty + String in_reply_to_user_id; + @JsonProperty + String in_reply_to_screen_name; + @JsonProperty + Object place; + @JsonProperty + User user; + @JsonProperty + String source; + @JsonProperty + String in_reply_to_user_id_str; + } + + static class User { + @JsonProperty + String name; + @JsonProperty + String profile_sidebar_border_color; + @JsonProperty + boolean profile_background_tile; + @JsonProperty + String profile_sidebar_fill_color; + @JsonProperty + Date created_at; + @JsonProperty + String location; + @JsonProperty + String profile_image_url; + @JsonProperty + boolean follow_request_sent; + @JsonProperty + String profile_link_color; + @JsonProperty + boolean is_translator; + @JsonProperty + String id_str; + @JsonProperty + int favourites_count; + @JsonProperty + boolean contributors_enabled; + @JsonProperty + String url; + @JsonProperty + boolean default_profile; + @JsonProperty + long utc_offset; + @JsonProperty + long id; + @JsonProperty + boolean profile_use_background_image; + @JsonProperty + int listed_count; + @JsonProperty + String lang; + @JsonProperty("protected") + @SerializedName("protected") + boolean isProtected; + @JsonProperty + int followers_count; + @JsonProperty + String profile_text_color; + @JsonProperty + String profile_background_color; + @JsonProperty + String time_zone; + @JsonProperty + String description; + @JsonProperty + boolean notifications; + @JsonProperty + boolean geo_enabled; + @JsonProperty + boolean verified; + @JsonProperty + String profile_background_image_url; + @JsonProperty + boolean defalut_profile_image; + @JsonProperty + int friends_count; + @JsonProperty + int statuses_count; + @JsonProperty + String screen_name; + @JsonProperty + boolean following; + @JsonProperty + boolean show_all_inline_media; + } + + static class Feed { + @JsonProperty + String id; + @JsonProperty + String title; + @JsonProperty + String description; + @JsonProperty("alternate") + @SerializedName("alternate") + List alternates; + @JsonProperty + long updated; + @JsonProperty + List items; + + @Override + public String toString() { + StringBuilder result = new StringBuilder() + .append(id) + .append("\n").append(title) + .append("\n").append(description) + .append("\n").append(alternates) + .append("\n").append(updated); + int i = 1; + for (Item item : items) { + result.append(i++).append(": ").append(item).append("\n\n"); + } + return result.toString(); + } + } + + static class Link { + @JsonProperty + String href; + + @Override + public String toString() { + return href; + } + } + + static class Item { + @JsonProperty + List categories; + @JsonProperty + String title; + @JsonProperty + long published; + @JsonProperty + long updated; + @JsonProperty("alternate") + @SerializedName("alternate") + List alternates; + @JsonProperty + Content content; + @JsonProperty + String author; + @JsonProperty + List likingUsers; + + @Override + public String toString() { + return title + + "\nauthor: " + author + + "\npublished: " + published + + "\nupdated: " + updated + + "\n" + content + + "\nliking users: " + likingUsers + + "\nalternates: " + alternates + + "\ncategories: " + categories; + } + } + + static class Content { + @JsonProperty + String content; + + @Override + public String toString() { + return content; + } + } + + static class ReaderUser { + @JsonProperty + String userId; + + @Override + public String toString() { + return userId; + } + } +} diff --git a/src/test/java/com/jsoniter/IterImplForStreamingTest.java b/src/test/java/com/jsoniter/IterImplForStreamingTest.java new file mode 100644 index 00000000..e0432d39 --- /dev/null +++ b/src/test/java/com/jsoniter/IterImplForStreamingTest.java @@ -0,0 +1,93 @@ +package com.jsoniter; + +import com.jsoniter.any.Any; +import com.jsoniter.spi.JsonException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import junit.framework.TestCase; +import org.junit.experimental.categories.Category; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +public class IterImplForStreamingTest extends TestCase { + + public void testReadMaxDouble() throws Exception { + String maxDouble = "1.7976931348623157e+308"; + JsonIterator iter = JsonIterator.parse("1.7976931348623157e+308"); + IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(iter); + String number = new String(numberChars.chars, 0, numberChars.charsLength); + assertEquals(maxDouble, number); + } + + @Category(StreamingCategory.class) + public void testLoadMore() throws IOException { + final String originalContent = "1234567890"; + final byte[] src = ("{\"a\":\"" + originalContent + "\"}").getBytes(); + + int initialBufferSize; + Any parsedString; + // Case #1: Data fits into initial buffer, autoresizing on + // Input must definitely fit into such large buffer + initialBufferSize = src.length * 2; + JsonIterator jsonIterator = JsonIterator.parse(getSluggishInputStream(src), initialBufferSize, 512); + jsonIterator.readObject(); + parsedString = jsonIterator.readAny(); + assertEquals(originalContent, parsedString.toString()); + // Check buffer was not expanded + assertEquals(initialBufferSize, jsonIterator.buf.length); + + // Case #2: Data does not fit into initial buffer, autoresizing off + initialBufferSize = originalContent.length() / 2; + jsonIterator = JsonIterator.parse(getSluggishInputStream(src), initialBufferSize, 0); + jsonIterator.readObject(); + try { + jsonIterator.readAny(); + fail("Expect to fail because buffer is too small."); + } catch (JsonException e) { + if (!e.getMessage().startsWith("loadMore")) { + throw e; + } + } + // Check buffer was not expanded + assertEquals(initialBufferSize, jsonIterator.buf.length); + + // Case #3: Data does fit into initial buffer, autoresizing on + initialBufferSize = originalContent.length() / 2; + int autoExpandBufferStep = initialBufferSize * 3; + jsonIterator = JsonIterator.parse(getSluggishInputStream(src), initialBufferSize, autoExpandBufferStep); + jsonIterator.readObject(); + parsedString = jsonIterator.readAny(); + assertEquals(originalContent, parsedString.toString()); + // Check buffer was expanded exactly once + assertEquals(initialBufferSize + autoExpandBufferStep, jsonIterator.buf.length); + + // Case #4: Data does not fit (but largest string does) into initial buffer, autoresizing on + initialBufferSize = originalContent.length() + 2; + jsonIterator = JsonIterator.parse(new ByteArrayInputStream(src), initialBufferSize, 0); + jsonIterator.readObject(); + parsedString = jsonIterator.readAny(); + assertEquals(originalContent, parsedString.toString()); + // Check buffer was expanded exactly once + assertEquals(initialBufferSize, jsonIterator.buf.length); + } + + private static InputStream getSluggishInputStream(final byte[] src) { + return new InputStream() { + int position = 0; + + @Override + public int read() throws IOException { + throw new NotImplementedException(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (position < src.length) { + b[off] = src[position++]; + return 1; + } + return -1; + } + }; + } +} diff --git a/src/test/java/com/jsoniter/StreamingCategory.java b/src/test/java/com/jsoniter/StreamingCategory.java new file mode 100644 index 00000000..73d5c887 --- /dev/null +++ b/src/test/java/com/jsoniter/StreamingCategory.java @@ -0,0 +1,4 @@ +package com.jsoniter; + +public interface StreamingCategory { +} diff --git a/src/test/java/com/jsoniter/TestAnnotation.java b/src/test/java/com/jsoniter/TestAnnotation.java index 562e4ab2..3992e3f7 100644 --- a/src/test/java/com/jsoniter/TestAnnotation.java +++ b/src/test/java/com/jsoniter/TestAnnotation.java @@ -1,75 +1,19 @@ package com.jsoniter; -import com.jsoniter.annotation.*; -import com.jsoniter.any.Any; -import com.jsoniter.spi.Decoder; -import com.jsoniter.spi.JsoniterSpi; +import com.jsoniter.annotation.JsonCreator; +import com.jsoniter.annotation.JsonProperty; +import com.jsoniter.spi.JsonException; import junit.framework.TestCase; import java.io.IOException; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; public class TestAnnotation extends TestCase { static { - JsoniterAnnotationSupport.enable(); -// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY); // JsonIterator.setMode(DecodingMode.REFLECTION_MODE); } - public static class TestObject1 { - @JsonProperty("field-1") - public int field1; - - @JsonIgnore - public int field2; - } - - public void test_rename() throws IOException { - JsonIterator iter = JsonIterator.parse("{'field-1': 100}".replace('\'', '"')); - TestObject1 obj = iter.read(TestObject1.class); - assertEquals(100, obj.field1); - } - - public void test_ignore() throws IOException { - JsonIterator iter = JsonIterator.parse("{'field2': 100}".replace('\'', '"')); - TestObject1 obj = iter.read(TestObject1.class); - assertEquals(0, obj.field2); - } - - public static class TestObject2 { - private int field1; - - @JsonCreator - public TestObject2(@JsonProperty("field1") int field1) { - this.field1 = field1; - } - } - - public void test_ctor() throws IOException { - JsonIterator iter = JsonIterator.parse("{'field1': 100}".replace('\'', '"')); - TestObject2 obj = iter.read(TestObject2.class); - assertEquals(100, obj.field1); - } - - public static class TestObject3 { - public int field1; - - @JsonCreator - private TestObject3() { - } - } - - public void test_private_ctor() throws IOException { - JsoniterSpi.registerTypeDecoder(TestObject3.class, ReflectionDecoderFactory.create(TestObject3.class)); - JsonIterator iter = JsonIterator.parse("{'field1': 100}".replace('\'', '"')); - TestObject3 obj = iter.read(TestObject3.class); - assertEquals(100, obj.field1); - } - public static class TestObject4 { private int field1; @@ -105,36 +49,6 @@ public void test_single_param_setter() throws IOException { assertEquals(100, obj.field1); } - public static class TestObject6 { - - private int field1; - - @JsonWrapper - public void initialize(@JsonProperty("field1") int field1) { - this.field1 = field1; - } - } - - public void test_multi_param_setter() throws IOException { - JsonIterator iter = JsonIterator.parse("{'field1': 100}".replace('\'', '"')); - TestObject6 obj = iter.read(TestObject6.class); - assertEquals(100, obj.field1); - } - - public static class TestObject7 { - @JsonProperty(required = true) - public int field1; - - @JsonMissingProperties - public List missingProperties; - } - - public void test_required_properties() throws IOException { - JsonIterator iter = JsonIterator.parse("{}"); - TestObject7 obj = iter.read(TestObject7.class); - assertEquals(Arrays.asList("field1"), obj.missingProperties); - } - public static class TestObject8 { @JsonCreator public TestObject8(@JsonProperty(required = true) int param1) { @@ -142,7 +56,7 @@ public TestObject8(@JsonProperty(required = true) int param1) { } } - public void test_missing_ctor_arg() throws IOException { + public void skip_missing_ctor_arg() throws IOException { JsonIterator iter = JsonIterator.parse("{}"); try { iter.read(TestObject8.class); @@ -152,121 +66,42 @@ public void test_missing_ctor_arg() throws IOException { } } - @JsonObject(asExtraForUnknownProperties = true) - public static class TestObject9 { - @JsonExtraProperties - public Map extraProperties; - } - - public void test_extra_properties() throws IOException { - JsonIterator iter = JsonIterator.parse("{\"field1\": 100}"); - TestObject9 obj = iter.read(TestObject9.class); - assertEquals(100, obj.extraProperties.get("field1").toInt()); - } - - public static class TestObject10 { - @JsonProperty(decoder = Decoder.StringIntDecoder.class) - public int field1; - } - - public void test_property_decoder() throws IOException { - JsonIterator iter = JsonIterator.parse("{\"field1\": \"100\"}"); - TestObject10 obj = iter.read(TestObject10.class); - assertEquals(100, obj.field1); - } - - public static class TestObject11 { - @JsonProperty(decoder = Decoder.StringIntDecoder.class) - public Integer field1; - } - - public void test_integer_property_decoder() throws IOException { - JsonIterator iter = JsonIterator.parse("{\"field1\": \"100\"}"); - TestObject11 obj = iter.read(TestObject11.class); - assertEquals(Integer.valueOf(100), obj.field1); - } - - public static class TestObject12 { - @JsonProperty(from = {"field_1", "field-1"}) + public static class TestObject17 { public int field1; - } - public void test_bind_from_multiple_names() throws IOException { - JsonIterator iter = JsonIterator.parse("{\"field-1\": 100, \"field-1\": 101}"); - TestObject12 obj = iter.read(TestObject12.class); - assertEquals(101, obj.field1); - } - - @JsonObject(asExtraForUnknownProperties = true) - public static class TestObject13 { - } - - public void test_unknown_properties() throws IOException { - JsonIterator iter = JsonIterator.parse("{\"field-1\": 100, \"field-1\": 101}"); - try { - iter.read(TestObject13.class); - fail(); - } catch (JsonException e) { - System.out.println(e); + public void setField1(int field1) { + this.field1 = field1; } - } - - public static class TestObject14 { - @JsonProperty(required = true) - public int field1; - @JsonMissingProperties - public List missingProperties; - } - - public void test_required_properties_not_missing() throws IOException { - JsonIterator iter = JsonIterator.parse("{\"field1\": 100}"); - TestObject14 obj = iter.read(TestObject14.class); - assertNull(obj.missingProperties); - assertEquals(100, obj.field1); - } - - @JsonObject(unknownPropertiesBlacklist = {"field1"}) - public static class TestObject15 { - } + @JsonCreator + public void initialize(@JsonProperty("field1") int field1) { - public void test_unknown_properties_blacklist() throws IOException { - JsonIterator iter = JsonIterator.parse("{\"field1\": 100}"); - try { - iter.read(TestObject15.class); - fail(); - } catch (JsonException e) { - System.out.println(e); } } - public static class TestObject16 { - @JsonProperty(implementation = LinkedList.class) - public List values; + public void test_name_conflict() throws IOException { + JsonIterator iter = JsonIterator.parse("{}"); + assertNotNull(iter.read(TestObject17.class)); } - public void test_specify_property() throws IOException { - JsonIterator iter = JsonIterator.parse("{\"values\": [100]}"); - TestObject16 obj = iter.read(TestObject16.class); - assertEquals(Arrays.asList(100), obj.values); - assertEquals(LinkedList.class, obj.values.getClass()); + public interface TestObject18Interface { + void setHello(A val); } - public static class TestObject17 { - public int field1; + public static class TestObject18 implements TestObject18Interface { - public void setField1(int field1) { - this.field1 = field1; - } - - @JsonCreator - public void initialize(@JsonProperty("field1") int field1) { + public int _val; + @Override + public void setHello(Integer val) { + _val = val; } } - public void test_name_conflict() throws IOException { - JsonIterator iter = JsonIterator.parse("{}"); - assertNotNull(iter.read(TestObject17.class)); + public void test_inherited_setter_is_not_duplicate() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"hello\":1}"); + TestObject18 obj = iter.read(TestObject18.class); + assertNotNull(obj); + assertEquals(1, obj._val); } } diff --git a/src/test/java/com/jsoniter/TestAnnotationJsonCreator.java b/src/test/java/com/jsoniter/TestAnnotationJsonCreator.java new file mode 100644 index 00000000..767b5b04 --- /dev/null +++ b/src/test/java/com/jsoniter/TestAnnotationJsonCreator.java @@ -0,0 +1,54 @@ +package com.jsoniter; + +import com.jsoniter.annotation.JsonCreator; +import com.jsoniter.annotation.JsonIgnore; +import com.jsoniter.annotation.JsonProperty; +import com.jsoniter.annotation.JsonWrapper; +import com.jsoniter.any.Any; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.Properties; + +public class TestAnnotationJsonCreator extends TestCase { + + + public static class TestObject2 { + private int field1; + + @JsonCreator + public TestObject2(@JsonProperty("field1") int field1) { + this.field1 = field1; + } + } + + public void test_ctor() throws IOException { + JsonIterator iter = JsonIterator.parse("{'field1': 100}".replace('\'', '"')); + TestObject2 obj = iter.read(TestObject2.class); + assertEquals(100, obj.field1); + } + + public static class TestObject { + + @JsonIgnore + private final String id; + @JsonIgnore + private final Properties properties; + + @JsonCreator + public TestObject(@JsonProperty("name") final String name) { + this.id = name; + properties = new Properties(); + } + + @JsonWrapper + public void setProperties(@JsonProperty("props") final Any props) { + // Set props + } + } + + public void test_ctor_and_setter_binding() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"name\": \"test\", \"props\": {\"val\": \"42\"}}"); + iter.read(TestObject.class); + } +} diff --git a/src/test/java/com/jsoniter/TestAnnotationJsonIgnore.java b/src/test/java/com/jsoniter/TestAnnotationJsonIgnore.java new file mode 100644 index 00000000..5c48e1b8 --- /dev/null +++ b/src/test/java/com/jsoniter/TestAnnotationJsonIgnore.java @@ -0,0 +1,66 @@ +package com.jsoniter; + +import com.jsoniter.annotation.JsonCreator; +import com.jsoniter.annotation.JsonIgnore; +import com.jsoniter.annotation.JsonProperty; +import com.jsoniter.spi.DecodingMode; +import junit.framework.TestCase; +import org.junit.Test; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.io.Serializable; + +public class TestAnnotationJsonIgnore extends TestCase { + + public static class TestObject1 { + @JsonIgnore + public int field2; + } + + public void test_ignore() throws IOException { + JsonIterator iter = JsonIterator.parse("{'field2': 100}".replace('\'', '"')); + TestObject1 obj = iter.read(TestObject1.class); + assertEquals(0, obj.field2); + } + + public static class TestObject2 { + @JsonIgnore + public Serializable field2; + } + + public void test_ignore_no_constructor_field() throws IOException { + JsonIterator iter = JsonIterator.parse("{'field2': 100}".replace('\'', '"')); + TestObject2 obj = iter.read(TestObject2.class); + assertNull(obj.field2); + } + + public static class TestObject3 { + String field1; + @JsonIgnore + ActionListener fieldXXX; + + @JsonCreator + public TestObject3(@JsonProperty("field2") final String field) { + field1 = null; + fieldXXX = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + System.out.println("field2 is " + field); + } + }; + } + + @Override + public String toString() { + return "field1=" + field1 + ", field2=" + fieldXXX; + } + } + + public void test_json_ignore_with_creator() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"field2\": \"test\"}"); + TestObject3 t = iter.read(TestObject3.class); + assertNotNull(t.fieldXXX); + } +} diff --git a/src/test/java/com/jsoniter/TestAnnotationJsonObject.java b/src/test/java/com/jsoniter/TestAnnotationJsonObject.java new file mode 100644 index 00000000..9e815048 --- /dev/null +++ b/src/test/java/com/jsoniter/TestAnnotationJsonObject.java @@ -0,0 +1,64 @@ +package com.jsoniter; + +import com.jsoniter.annotation.JsonExtraProperties; +import com.jsoniter.annotation.JsonObject; +import com.jsoniter.any.Any; +import com.jsoniter.spi.JsonException; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.Map; + +public class TestAnnotationJsonObject extends TestCase { + + @JsonObject(asExtraForUnknownProperties = true) + public static class TestObject9 { + @JsonExtraProperties + public Map extraProperties; + } + + public void test_extra_properties() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"field1\": 100}"); + TestObject9 obj = iter.read(TestObject9.class); + assertEquals(100, obj.extraProperties.get("field1").toInt()); + } + + @JsonObject(asExtraForUnknownProperties = true) + public static class TestObject13 { + } + + public void test_unknown_properties() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"field-1\": 100, \"field-1\": 101}"); + try { + iter.read(TestObject13.class); + fail(); + } catch (JsonException e) { + System.out.println(e); + } + } + + @JsonObject(unknownPropertiesBlacklist = {"field1"}) + public static class TestObject15 { + } + + public void test_unknown_properties_blacklist() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"field1\": 100}"); + try { + iter.read(TestObject15.class); + fail(); + } catch (JsonException e) { + System.out.println(e); + } + } + + @JsonObject(asExtraForUnknownProperties = true) + public static class TestObject14 { + public int id; + } + + public void test_no_unknown_properties() throws IOException { + String json = "{ \"id\": 100 }"; + TestObject14 obj = JsonIterator.deserialize(json, TestObject14.class); + assertEquals(100, obj.id); + } +} diff --git a/src/test/java/com/jsoniter/TestAnnotationJsonProperty.java b/src/test/java/com/jsoniter/TestAnnotationJsonProperty.java new file mode 100644 index 00000000..6268c422 --- /dev/null +++ b/src/test/java/com/jsoniter/TestAnnotationJsonProperty.java @@ -0,0 +1,187 @@ +package com.jsoniter; + +import com.jsoniter.annotation.JsonCreator; +import com.jsoniter.annotation.JsonMissingProperties; +import com.jsoniter.annotation.JsonProperty; +import com.jsoniter.fuzzy.StringIntDecoder; +import com.jsoniter.output.JsonStream; +import com.jsoniter.spi.DecodingMode; +import com.jsoniter.spi.JsonException; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +public class TestAnnotationJsonProperty extends TestCase { + + public static class TestObject1 { + @JsonProperty(from = {"field-1"}) + public int field1; + } + + public void test_rename() throws IOException { + JsonIterator iter = JsonIterator.parse("{'field-1': 100}".replace('\'', '"')); + TestObject1 obj = iter.read(TestObject1.class); + assertEquals(100, obj.field1); + } + + public static class TestObject2 { + @JsonProperty(required = true) + public int field1; + + @JsonMissingProperties + public List missingProperties; + } + + public void test_required_properties() throws IOException { + JsonIterator iter = JsonIterator.parse("{}"); + TestObject2 obj = iter.read(TestObject2.class); + assertEquals(Arrays.asList("field1"), obj.missingProperties); + } + + public static class TestObject3 { + @JsonProperty(decoder = StringIntDecoder.class) + public int field1; + } + + public void test_property_decoder() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"field1\": \"100\"}"); + TestObject3 obj = iter.read(TestObject3.class); + assertEquals(100, obj.field1); + } + + public static class TestObject4 { + @JsonProperty(decoder = StringIntDecoder.class) + public Integer field1; + } + + public void test_integer_property_decoder() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"field1\": \"100\"}"); + TestObject4 obj = iter.read(TestObject4.class); + assertEquals(Integer.valueOf(100), obj.field1); + } + + public static class TestObject5 { + @JsonProperty(from = {"field_1", "field-1"}) + public int field1; + } + + public void test_bind_from_multiple_names() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"field-1\": 100, \"field-1\": 101}"); + TestObject5 obj = iter.read(TestObject5.class); + assertEquals(101, obj.field1); + } + + public static class TestObject6 { + @JsonProperty(required = true) + public int field1; + + @JsonMissingProperties + public List missingProperties; + } + + public void test_required_properties_not_missing() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"field1\": 100}"); + TestObject6 obj = iter.read(TestObject6.class); + assertNull(obj.missingProperties); + assertEquals(100, obj.field1); + } + + public static class TestObject7 { + @JsonProperty(implementation = LinkedList.class) + public List values; + } + + public void test_specify_property() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"values\": [100]}"); + TestObject7 obj = iter.read(TestObject7.class); + assertEquals(Arrays.asList(100), obj.values); + assertEquals(LinkedList.class, obj.values.getClass()); + } + + public static class TestObject8 { + public String error; + @JsonProperty(value = "rs", required = true) + public boolean result; + @JsonProperty(value = "code",required = true) + public int code2; + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("code="); + builder.append(code2); + builder.append(" rs="); + builder.append(result); + return builder.toString(); + + } + } + + public void test_required() throws IOException { + String test ="{\"rs\":true,\"code\":200}"; + TestObject8 entity = JsonIterator.deserialize(test, TestObject8.class); + assertEquals(200, entity.code2); + } + + public static class TestObject9 { + private String field1 = "hello"; + + public String getField1() { + return field1; + } + + @JsonProperty("field-1") + public void setField1(String field1) { + this.field1 = field1; + } + } + + public void test_getter_and_setter() throws IOException { + String test ="{\"field-1\":\"hi\"}"; + TestObject9 entity = JsonIterator.deserialize(test, TestObject9.class); + assertEquals("hi", entity.getField1()); + } + + public static class TestObject10 { + private int field; + + @JsonCreator + public TestObject10(@JsonProperty("hello") int field) { + this.field = field; + } + + public int getField() { + return field; + } + } + + public void test_creator_with_json_property() { + String input = "{\"hello\":100}"; + TestObject10 obj = JsonIterator.deserialize(input, TestObject10.class); + assertEquals(100, obj.field); + assertEquals("{\"field\":100}", JsonStream.serialize(obj)); + } + + public static class TestObject11 { + @JsonProperty("hello") + public int field; + + public int getField() { + return field; + } + + public void setField(int field) { + this.field = field; + } + } + + public void test_field_and_getter_setter() { + String input = "{\"hello\":100}"; + TestObject11 obj = JsonIterator.deserialize(input, TestObject11.class); + assertEquals(100, obj.field); + } + +} diff --git a/src/test/java/com/jsoniter/TestAnnotationJsonWrapper.java b/src/test/java/com/jsoniter/TestAnnotationJsonWrapper.java new file mode 100644 index 00000000..eb9d2e07 --- /dev/null +++ b/src/test/java/com/jsoniter/TestAnnotationJsonWrapper.java @@ -0,0 +1,78 @@ +package com.jsoniter; + +import com.jsoniter.annotation.JsonIgnore; +import com.jsoniter.annotation.JsonProperty; +import com.jsoniter.annotation.JsonWrapper; +import com.jsoniter.annotation.JsonWrapperType; +import com.jsoniter.spi.DecodingMode; +import junit.framework.TestCase; + +import java.io.IOException; + +public class TestAnnotationJsonWrapper extends TestCase { + + static { +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); +// JsonIterator.setMode(DecodingMode.REFLECTION_MODE); + } + + public static class TestObject1 { + + private int _field1; + + @JsonWrapper + public void initialize(@JsonProperty("field1") int field1) { + this._field1 = field1; + } + } + + public void test_binding() throws IOException { + JsonIterator iter = JsonIterator.parse("{'field1': 100}".replace('\'', '"')); + TestObject1 obj = iter.read(TestObject1.class); + assertEquals(100, obj._field1); + } + + public static class TestObject2 { + + private int _field1; + + @JsonWrapper(JsonWrapperType.KEY_VALUE) + public void setProperties(String key, Object value) { + if (key.equals("field1")) { + _field1 = ((Long) value).intValue(); + } + } + } + + public void test_key_value() throws IOException { + JsonIterator iter = JsonIterator.parse("{'field1': 100}".replace('\'', '"')); + TestObject2 obj = iter.read(TestObject2.class); + assertEquals(100, obj._field1); + } + + public static class AAA { + @JsonProperty("name_1") + public String name; + + @JsonIgnore + public String partA; + @JsonIgnore + public String partB; + + @JsonWrapper + public void foreignFromJson(@JsonProperty(value = "parts", from ={"p2"}, required = false) String parts) { + if(parts == null){ + return; + } + String[] ps = parts.split(","); + partA = ps[0]; + partB = ps.length > 1 ? ps[1] : null; + } + } + + public void test_issue_104() { + String jsonStr = "{'name':'aaa', 'name_1':'bbb'}".replace('\'', '\"'); + AAA aaa = JsonIterator.deserialize(jsonStr, AAA.class); + assertEquals("bbb", aaa.name); + } +} diff --git a/src/test/java/com/jsoniter/TestArray.java b/src/test/java/com/jsoniter/TestArray.java index f52c8254..92ea6cf4 100644 --- a/src/test/java/com/jsoniter/TestArray.java +++ b/src/test/java/com/jsoniter/TestArray.java @@ -5,7 +5,9 @@ import junit.framework.TestCase; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; import static org.junit.Assert.assertArrayEquals; @@ -13,7 +15,7 @@ public class TestArray extends TestCase { static { -// JsonIterator.setMode(DecodingMode.REFLECTION_MODE); +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); } public void test_empty_array() throws IOException { @@ -44,11 +46,21 @@ public void test_one_element() throws IOException { }); assertEquals(Arrays.asList(1), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); assertEquals(1, iter.readAny().toInt(0)); + iter.reset(iter.buf); + final List values = new ArrayList(); + iter.readArrayCB(new JsonIterator.ReadArrayCallback() { + @Override + public boolean handle(JsonIterator iter, Object attachment) throws IOException { + values.add(iter.readInt()); + return true; + } + }, null); + assertEquals(Arrays.asList(1), values); } public void test_two_elements() throws IOException { @@ -66,11 +78,13 @@ public void test_two_elements() throws IOException { }); assertEquals(Arrays.asList(1, 2), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0, 2.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1, 2}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); assertEquals(1, iter.readAny().toInt(0)); + iter = JsonIterator.parse(" [ 1 , null, 2 ] "); + assertEquals(Arrays.asList(1, null, 2), iter.read()); } public void test_three_elements() throws IOException { @@ -90,7 +104,7 @@ public void test_three_elements() throws IOException { }); assertEquals(Arrays.asList(1, 2, 3), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0, 2.0, 3.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1, 2, 3}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); @@ -116,7 +130,7 @@ public void test_four_elements() throws IOException { }); assertEquals(Arrays.asList(1, 2, 3, 4), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0, 2.0, 3.0, 4.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1, 2, 3, 4}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); @@ -144,7 +158,7 @@ public void test_five_elements() throws IOException { }); assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0, 2.0, 3.0, 4.0, 5.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1, 2, 3, 4, 5}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); @@ -160,4 +174,29 @@ public void test_boolean_array() throws IOException { JsonIterator iter = JsonIterator.parse("[true, false, true]"); assertArrayEquals(new boolean[]{true, false, true}, iter.read(boolean[].class)); } + + public void test_iterator() throws IOException { + Any any = JsonIterator.deserialize("[1,2,3,4]"); + Iterator iter = any.iterator(); + assertEquals(1, iter.next().toInt()); + iter = any.iterator(); + assertEquals(1, iter.next().toInt()); + assertEquals(2, iter.next().toInt()); + iter = any.iterator(); + assertEquals(1, iter.next().toInt()); + assertEquals(2, iter.next().toInt()); + assertEquals(3, iter.next().toInt()); + iter = any.iterator(); + assertEquals(1, iter.next().toInt()); + assertEquals(2, iter.next().toInt()); + assertEquals(3, iter.next().toInt()); + assertEquals(4, iter.next().toInt()); + assertFalse(iter.hasNext()); + } + + public void test_array_lazy_any_to_string() { + Any any = JsonIterator.deserialize("[1,2,3]"); + any.asList().add(Any.wrap(4)); + assertEquals("[1,2,3,4]", any.toString()); + } } diff --git a/src/test/java/com/jsoniter/TestBoolean.java b/src/test/java/com/jsoniter/TestBoolean.java new file mode 100644 index 00000000..71954ff4 --- /dev/null +++ b/src/test/java/com/jsoniter/TestBoolean.java @@ -0,0 +1,28 @@ +package com.jsoniter; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class TestBoolean extends TestCase { + @org.junit.experimental.categories.Category(StreamingCategory.class) + public void test_streaming() throws IOException { + JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream("[true,false,null,true]".getBytes()), 3); + iter.readArray(); + assertTrue(iter.readBoolean()); + iter.readArray(); + assertFalse(iter.readBoolean()); + iter.readArray(); + assertTrue(iter.readNull()); + iter.readArray(); + assertTrue(iter.readBoolean()); + } + + public void test_non_streaming() throws IOException { + assertTrue(JsonIterator.parse("true").readBoolean()); + assertFalse(JsonIterator.parse("false").readBoolean()); + assertTrue(JsonIterator.parse("null").readNull()); + assertFalse(JsonIterator.parse("false").readNull()); + } +} diff --git a/src/test/java/com/jsoniter/TestCustomizeType.java b/src/test/java/com/jsoniter/TestCustomizeType.java index 230b4d9b..55d79696 100644 --- a/src/test/java/com/jsoniter/TestCustomizeType.java +++ b/src/test/java/com/jsoniter/TestCustomizeType.java @@ -11,45 +11,11 @@ public class TestCustomizeType extends TestCase { - public static class MyDate { - Date date; - } - static { // JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY); // JsonIterator.setMode(DecodingMode.REFLECTION_MODE); } - public void test_direct() throws IOException { - JsoniterSpi.registerTypeDecoder(MyDate.class, new Decoder() { - @Override - public Object decode(final JsonIterator iter) throws IOException { - return new MyDate() {{ - date = new Date(iter.readLong()); - }}; - } - }); - JsonIterator iter = JsonIterator.parse("1481365190000"); - MyDate date = iter.read(MyDate.class); - assertEquals(1481365190000L, date.date.getTime()); - } - - public static class FieldWithMyDate { - public MyDate field; - } - - public void test_as_field_type() throws IOException { - JsonIterator iter = JsonIterator.parse("{'field': 1481365190000}".replace('\'', '"')); - FieldWithMyDate obj = iter.read(FieldWithMyDate.class); - assertEquals(1481365190000L, obj.field.date.getTime()); - } - - public void test_as_array_element() throws IOException { - JsonIterator iter = JsonIterator.parse("[1481365190000]"); - MyDate[] dates = iter.read(MyDate[].class); - assertEquals(1481365190000L, dates[0].date.getTime()); - } - public static class MyDate2 { Date date; } diff --git a/src/test/java/com/jsoniter/TestDemo.java b/src/test/java/com/jsoniter/TestDemo.java index a6d5ab8b..cf42cb87 100644 --- a/src/test/java/com/jsoniter/TestDemo.java +++ b/src/test/java/com/jsoniter/TestDemo.java @@ -1,6 +1,10 @@ package com.jsoniter; +import com.jsoniter.annotation.JsonProperty; import com.jsoniter.any.Any; +import com.jsoniter.fuzzy.MaybeEmptyArrayDecoder; +import com.jsoniter.fuzzy.MaybeStringLongDecoder; +import com.jsoniter.output.JsonStream; import com.jsoniter.spi.Decoder; import com.jsoniter.spi.EmptyExtension; import com.jsoniter.spi.JsoniterSpi; @@ -9,7 +13,8 @@ import java.io.IOException; import java.lang.reflect.Type; -import java.util.Date; +import java.util.HashMap; +import java.util.List; public class TestDemo extends TestCase { public void test_bind_api() throws IOException { @@ -53,6 +58,10 @@ public void test_iterator_api_and_bind() throws IOException { System.out.println(user); } + public static class TestObject2 { + + } + public void test_empty_array_as_null() throws IOException { JsoniterSpi.registerExtension(new EmptyExtension() { @Override @@ -61,7 +70,7 @@ public Decoder createDecoder(final String cacheKey, final Type type) { // avoid infinite loop return null; } - if (type != Date.class) { + if (type != TestObject2.class) { return null; } return new Decoder() { @@ -85,6 +94,138 @@ public Object decode(JsonIterator iter1) throws IOException { } }); JsonIterator iter = JsonIterator.parse("[]"); - assertNull(iter.read(Date.class)); + assertNull(iter.read(TestObject2.class)); + } + + public static class Order { + @JsonProperty(decoder = MaybeStringLongDecoder.class) + public long order_id; + @JsonProperty(decoder = MaybeEmptyArrayDecoder.class) + public OrderDetails order_details; + } + + public static class OrderDetails { + public String pay_type; + } + + public void test_iterator() throws IOException { + JsonIterator iter = JsonIterator.parse("{'numbers': ['1', '2', ['3', '4']]}".replace('\'', '"')); + assertEquals("numbers", iter.readObject()); + assertTrue(iter.readArray()); + assertEquals("1", iter.readString()); + assertTrue(iter.readArray()); + assertEquals("2", iter.readString()); + assertTrue(iter.readArray()); + assertEquals(ValueType.ARRAY, iter.whatIsNext()); + assertTrue(iter.readArray()); // start inner array + assertEquals(ValueType.STRING, iter.whatIsNext()); + assertEquals("3", iter.readString()); + assertTrue(iter.readArray()); + assertEquals("4", iter.readString()); + assertFalse(iter.readArray()); // end inner array + assertFalse(iter.readArray()); // end outer array + assertNull(iter.readObject()); // end object + } + + public void test_any_is_fun() throws IOException { + Any any = JsonIterator.deserialize("{'numbers': ['1', '2', ['3', '4']]}".replace('\'', '"')); + any.get("numbers").asList().add(Any.wrap("hello")); + assertEquals("{'numbers':['1', '2', ['3', '4'],'hello']}".replace('\'', '"'), JsonStream.serialize(any)); + any = JsonIterator.deserialize("{'error': 'failed'}".replace('\'', '"')); + assertFalse(any.toBoolean("success")); + any = JsonIterator.deserialize("{'success': true}".replace('\'', '"')); + assertTrue(any.toBoolean("success")); + any = JsonIterator.deserialize("{'success': 'false'}".replace('\'', '"')); + assertFalse(any.toBoolean("success")); + any = JsonIterator.deserialize("[{'score':100}, {'score':102}]".replace('\'', '"')); + assertEquals("[100,102]", JsonStream.serialize(any.get('*', "score"))); + any = JsonIterator.deserialize("[{'score':100}, {'score':[102]}]".replace('\'', '"')); + assertEquals("[{},{'score':102}]".replace('\'', '"'), JsonStream.serialize(any.get('*', '*', 0))); + any = JsonIterator.deserialize("[{'score':100}, {'score':102}]".replace('\'', '"')); + assertEquals(Long.class, any.get(0, "score").object().getClass()); + any = JsonIterator.deserialize("[{'score':100}, {'score':102}]".replace('\'', '"')); + assertEquals(ValueType.INVALID, any.get(0, "score", "number").valueType()); + any = JsonIterator.deserialize("[{'score':100}, {'score':102}]".replace('\'', '"')); + for (Any record : any) { + Any.EntryIterator entries = record.entries(); + while (entries.next()) { + System.out.println(entries.key()); + System.out.println(entries.value()); + } + } + } + + public static class TestObject { + public String body; + public int commentCount; + } + + public void test_utf8() { + String input = "{\"body\":\"یبل تیبلتیبمسش یبمک سشیمب سشیکمب تشسکمیبنمسیتبمسشتیب منشستمتبیملتیبملتیبمتلیمبلت یبلتیبل ینبنن اسی باسیش نباسشینباشسینبشسنتیب شسنیاب نشسیابنسشتیابنتسشیابنسشیابنسیشابنسشیاب نسشیاب سشیب سشیبن ت سینبسیبنسیشاب نسیاب سیاب نسیتبا سینا سیا بسیاب نستیشاب نستیبسی\",\"commentCount\":0,\"doILike\":false,\"doISuggest\":false,\"likeCount\":1,\"rowId\":\"58bf6ed1c8015f0bd4422c70\",\"specialLabel\":0,\"submitDate\":\"2017-03-08T02:39:13.568Z\",\"suggestCount\":0,\"title\":\"تست می باشد.\",\"type\":1,\"url\":[\"images/cell/490661220.jpg\"],\"username\":\"mahdihp\"}"; + TestObject obj = JsonIterator.deserialize(input, TestObject.class); + assertEquals(0, obj.commentCount); + } + + public void test_deserialize() { + String str = "{\"port\":13110} "; + JsonIterator.deserialize(str.getBytes(), HashMap.class); + } + + public static class CollectionResponse { + public List results; + } + + public static class Feed { + public String id; + public String owner; + public String name; + } + + public void test_generics() { + CollectionResponse objs = JsonIterator.deserialize("{\n" + + "\"count\": 1,\n" + + "\"next\": null,\n" + + "\"previous\": null,\n" + + "\"results\": [\n" + + "{\n" + + "\"id\": \"f560fccb-4020-43c1-8a27-92507ef625bd\",\n" + + "\"search_terms\": [\n" + + "\"gigi hadid\"\n" + + "],\n" + + "\"owner\": \"...\",\n" + + "\"egress_nodes\": [\n" + + "\"DE\"\n" + + "],\n" + + "\"status\": \"ACTIVE\",\n" + + "\"expires_at\": null,\n" + + "\"available_sources\": [\n" + + "\"92c784ae-b7bf-4434-a6cc-740109d91cc8\"\n" + + "],\n" + + "\"available_egress_nodes\": [\n" + + "\"DE\"\n" + + "],\n" + + "\"created_at\": \"2017-07-27T13:29:20.935108Z\",\n" + + "\"name\": \"Test\",\n" + + "\"description\": \"\",\n" + + "\"start_date\": null,\n" + + "\"end_date\": null,\n" + + "\"match_all_include\": false,\n" + + "\"velocity\": 0.0666666666666667,\n" + + "\"storage_consumption\": 0.000011026778,\n" + + "\"consumption\": 0.000120833333333333,\n" + + "\"persistence_enabled\": true,\n" + + "\"sources\": [\n" + + "\"92c784ae-b7bf-4434-a6cc-740109d91cc8\"\n" + + "],\n" + + "\"permissions\": {\n" + + "\"has_read_access\": true,\n" + + "\"has_write_access\": true,\n" + + "\"has_share_access\": true,\n" + + "\"has_ownership\": true\n" + + "}\n" + + "}\n" + + "]\n" + + "}", new TypeLiteral>(){}); + assertEquals("f560fccb-4020-43c1-8a27-92507ef625bd", objs.results.get(0).id); } } diff --git a/src/test/java/com/jsoniter/TestExisting.java b/src/test/java/com/jsoniter/TestExisting.java index d54a788b..a002d98b 100644 --- a/src/test/java/com/jsoniter/TestExisting.java +++ b/src/test/java/com/jsoniter/TestExisting.java @@ -24,9 +24,10 @@ public void test_direct_reuse() throws IOException { TestObj1 testObj = new TestObj1(); testObj.field2 = "world"; JsonIterator iter = JsonIterator.parse("{ 'field1' : 'hello' }".replace('\'', '"')); + TestObj1 oldObj = testObj; testObj = iter.read(testObj); assertEquals("hello", testObj.field1); - assertEquals("world", testObj.field2); + assertEquals(System.identityHashCode(oldObj), System.identityHashCode(testObj)); } public static class TestObj2 { @@ -39,10 +40,11 @@ public void test_indirect_reuse() throws IOException { testObj.field4 = new TestObj1(); testObj.field4.field1 = "world"; JsonIterator iter = JsonIterator.parse("{ 'field3' : 'hello', 'field4': {'field2': 'hello'} }".replace('\'', '"')); + TestObj2 oldObj = testObj; testObj = iter.read(testObj); assertEquals("hello", testObj.field3); assertEquals("hello", testObj.field4.field2); - assertEquals("world", testObj.field4.field1); + assertEquals(System.identityHashCode(oldObj), System.identityHashCode(testObj)); } public void test_reuse_list() throws IOException { diff --git a/src/test/java/com/jsoniter/TestFloat.java b/src/test/java/com/jsoniter/TestFloat.java new file mode 100644 index 00000000..5fc0f851 --- /dev/null +++ b/src/test/java/com/jsoniter/TestFloat.java @@ -0,0 +1,100 @@ +package com.jsoniter; + +import junit.framework.TestCase; +import org.junit.experimental.categories.Category; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigDecimal; + +public class TestFloat extends TestCase { + + private boolean isStreaming; + + public void test_positive_negative() throws IOException { + // positive + assertEquals(12.3f, parseFloat("12.3,")); + assertEquals(729212.0233f, parseFloat("729212.0233,")); + assertEquals(12.3d, parseDouble("12.3,")); + assertEquals(729212.0233d, parseDouble("729212.0233,")); + // negative + assertEquals(-12.3f, parseFloat("-12.3,")); + assertEquals(-12.3d, parseDouble("-12.3,")); + } + + public void test_long_double() throws IOException { + double d = JsonIterator.deserialize("4593560419846153055", double.class); + assertEquals(4593560419846153055d, d, 0.1); + } + + public void test_ieee_754() throws IOException { + assertEquals(0.00123f, parseFloat("123e-5,")); + assertEquals(0.00123d, parseDouble("123e-5,")); + } + + public void test_decimal_places() throws IOException { + assertEquals(Long.MAX_VALUE, parseFloat("9223372036854775807,"), 0.01f); + assertEquals(Long.MAX_VALUE, parseDouble("9223372036854775807,"), 0.01f); + assertEquals(Long.MIN_VALUE, parseDouble("-9223372036854775808,"), 0.01f); + assertEquals(9923372036854775807f, parseFloat("9923372036854775807,"), 0.01f); + assertEquals(-9923372036854775808f, parseFloat("-9923372036854775808,"), 0.01f); + assertEquals(9923372036854775807d, parseDouble("9923372036854775807,"), 0.01f); + assertEquals(-9923372036854775808d, parseDouble("-9923372036854775808,"), 0.01f); + assertEquals(720368.54775807f, parseFloat("720368.54775807,"), 0.01f); + assertEquals(-720368.54775807f, parseFloat("-720368.54775807,"), 0.01f); + assertEquals(720368.54775807d, parseDouble("720368.54775807,"), 0.01f); + assertEquals(-720368.54775807d, parseDouble("-720368.54775807,"), 0.01f); + assertEquals(72036.854775807f, parseFloat("72036.854775807,"), 0.01f); + assertEquals(72036.854775807d, parseDouble("72036.854775807,"), 0.01f); + assertEquals(720368.54775807f, parseFloat("720368.547758075,"), 0.01f); + assertEquals(720368.54775807d, parseDouble("720368.547758075,"), 0.01f); + } + + public void test_combination_of_dot_and_exponent() throws IOException { + double v = JsonIterator.parse("8.37377E9").readFloat(); + assertEquals(Double.valueOf("8.37377E9"), v, 1000d); + } + + @Category(StreamingCategory.class) + public void test_streaming() throws IOException { + isStreaming = true; + test_positive_negative(); + test_decimal_places(); + } + + private float parseFloat(String input) throws IOException { + if (isStreaming) { + return JsonIterator.parse(new ByteArrayInputStream(input.getBytes()), 2).readFloat(); + } else { + return JsonIterator.parse(input).readFloat(); + } + } + + private double parseDouble(String input) throws IOException { + if (isStreaming) { + return JsonIterator.parse(new ByteArrayInputStream(input.getBytes()), 2).readDouble(); + } else { + return JsonIterator.parse(input).readDouble(); + } + } + + public void testBigDecimal() { + BigDecimal number = JsonIterator.deserialize("100.1", BigDecimal.class); + assertEquals(new BigDecimal("100.1"), number); + } + + public void testChooseDouble() { + Object number = JsonIterator.deserialize("1.1", Object.class); + assertEquals(1.1, number); + number = JsonIterator.deserialize("1.0", Object.class); + assertEquals(1.0, number); + } + + public void testInfinity() { + assertTrue(JsonIterator.deserialize("\"-infinity\"", Double.class) == Double.NEGATIVE_INFINITY); + assertTrue(JsonIterator.deserialize("\"-infinity\"", Float.class) == Float.NEGATIVE_INFINITY); + assertTrue(JsonIterator.deserialize("\"infinity\"", Double.class) == Double.POSITIVE_INFINITY); + assertTrue(JsonIterator.deserialize("\"infinity\"", Float.class) == Float.POSITIVE_INFINITY); + } + +} diff --git a/src/test/java/com/jsoniter/TestGenerics.java b/src/test/java/com/jsoniter/TestGenerics.java index 6a2b4542..446dcc7f 100644 --- a/src/test/java/com/jsoniter/TestGenerics.java +++ b/src/test/java/com/jsoniter/TestGenerics.java @@ -1,9 +1,6 @@ package com.jsoniter; -import com.jsoniter.spi.Binding; -import com.jsoniter.spi.ClassDescriptor; -import com.jsoniter.spi.JsoniterSpi; -import com.jsoniter.spi.TypeLiteral; +import com.jsoniter.spi.*; import junit.framework.TestCase; import java.io.IOException; @@ -14,7 +11,7 @@ public class TestGenerics extends TestCase { static { -// JsonIterator.setMode(DecodingMode.REFLECTION_MODE); +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY); } public void test_int_list() throws IOException { @@ -52,11 +49,17 @@ public void test_string_map() throws IOException { assertEquals("world", val.get("hello")); } + public void test_integer_map() throws IOException { + JsonIterator iter = JsonIterator.parse("{'hello': 1}".replace('\'', '"')); + Map val = iter.read(new TypeLiteral>() { + }); + assertEquals(Integer.valueOf(1), val.get("hello")); + } + public void test_list_of_list() throws Exception { JsonIterator iter = JsonIterator.parse("[[1,2],[3,4]]"); List> listOfList = iter.read(new TypeLiteral>>() { }); - System.out.println(listOfList); assertEquals(Integer.valueOf(4), listOfList.get(1).get(1)); } @@ -72,12 +75,15 @@ public static class Class1 { public B[] field2; public List[] field3; public List field4; + public List>> getField6() { return null; } + public T getField7() { return null; } + public void setField8(List a) { } } @@ -90,23 +96,48 @@ public static class Class3 extends Class2 { } public void test_generic_super_class() throws IOException { - ClassDescriptor desc = JsoniterSpi.getDecodingClassDescriptor(Class3.class, true); + ClassDescriptor desc = ClassDescriptor.getDecodingClassDescriptor(new ClassInfo(Class3.class), true); Map fieldDecoderCacheKeys = new HashMap(); for (Binding field : desc.allDecoderBindings()) { fieldDecoderCacheKeys.put(field.name, field.valueTypeLiteral.getDecoderCacheKey()); } - for (Binding field : JsoniterSpi.getEncodingClassDescriptor(Class3.class, true).getters) { + for (Binding field : ClassDescriptor.getEncodingClassDescriptor(new ClassInfo(Class3.class), true).getters) { fieldDecoderCacheKeys.put(field.name, field.valueTypeLiteral.getDecoderCacheKey()); } - assertEquals(new HashMap() {{ - put("field1", "decoder.java.util.List_java.lang.String"); - put("field2", "decoder.java.lang.Integer_array"); - put("field3", "decoder.java.util.List_java.lang.Integer_array"); - put("field4", "decoder.java.util.List_java.lang.String_array"); - put("field5", "decoder.java.lang.Float"); - put("field6", "decoder.java.util.List_java.util.Map_java.lang.String_java.util.List_java.lang.Integer"); - put("field7", "decoder.java.lang.Object"); - put("field8", "decoder.java.util.List_java.lang.String"); - }}, fieldDecoderCacheKeys); + assertTrue(fieldDecoderCacheKeys.get("field1").endsWith("decoder.java.util.List_java.lang.String")); + assertTrue(fieldDecoderCacheKeys.get("field2").endsWith("decoder.java.lang.Integer_array")); + assertTrue(fieldDecoderCacheKeys.get("field3").endsWith("decoder.java.util.List_java.lang.Integer_array")); + assertTrue(fieldDecoderCacheKeys.get("field4").endsWith("decoder.java.util.List_java.lang.String_array")); + assertTrue(fieldDecoderCacheKeys.get("field5").endsWith("decoder.java.lang.Float")); + assertTrue(fieldDecoderCacheKeys.get("field6").endsWith("decoder.java.util.List_java.util.Map_java.lang.String_java.util.List_java.lang.Integer")); + assertTrue(fieldDecoderCacheKeys.get("field7").endsWith("decoder.java.lang.Object")); + assertTrue(fieldDecoderCacheKeys.get("field8").endsWith("decoder.java.util.List_java.lang.String")); + } + + public static class NetRes { + public int code; + public String desc; + public T results; + } + + public static class User { + public String name; + public int age; + } + + public void test_issue_103() { + String json = "{'code':1, 'desc':'OK', 'results':{'name':'aaa', 'age':18}}".replace('\'', '\"'); + NetRes res = JsonIterator.deserialize(json, new TypeLiteral>() { + }); + assertEquals(User.class, res.results.getClass()); + } + + public static class TestObject7 { + public List field; + } + + public void test_wildcard() throws IOException { + TestObject7 obj = JsonIterator.deserialize("{\"field\":[1]}", TestObject7.class); + assertEquals(1, obj.field.get(0)); } } diff --git a/src/test/java/com/jsoniter/TestGson.java b/src/test/java/com/jsoniter/TestGson.java new file mode 100644 index 00000000..bc7598da --- /dev/null +++ b/src/test/java/com/jsoniter/TestGson.java @@ -0,0 +1,268 @@ +package com.jsoniter; + +import com.google.gson.*; +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.extra.GsonCompatibilityMode; +import junit.framework.TestCase; + +import java.lang.reflect.Field; +import java.util.Date; +import java.util.TimeZone; + +public class TestGson extends TestCase { + + public static class TestObject1 { + @SerializedName("field-1") + public String field1; + } + + public void test_SerializedName() { + Gson gson = new Gson(); + TestObject1 obj = gson.fromJson("{\"field-1\":\"hello\"}", TestObject1.class); + assertEquals("hello", obj.field1); + obj = JsonIterator.deserialize(new GsonCompatibilityMode.Builder().build(), + "{\"field-1\":\"hello\"}", TestObject1.class); + assertEquals("hello", obj.field1); + } + + public static class TestObject2 { + @Expose(deserialize = false) + public String field1; + } + + public void test_Expose() { + // test if the iterator reuse will keep right config cache + JsonIterator.deserialize(new GsonCompatibilityMode.Builder().build(), + "{\"field-1\":\"hello\"}", TestObject2.class); + Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + TestObject2 obj = gson.fromJson("{\"field1\":\"hello\"}", TestObject2.class); + assertNull(obj.field1); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .excludeFieldsWithoutExposeAnnotation() + .build(); + obj = JsonIterator.deserialize(config, + "{\"field1\":\"hello\"}", TestObject2.class); + assertNull(obj.field1); + } + +// public void test_setDateFormat_no_op() { +// TimeZone orig = TimeZone.getDefault(); +// try { +// TimeZone.setDefault(TimeZone.getTimeZone("UTC")); +// Gson gson = new GsonBuilder().create(); +// Date obj = gson.fromJson("\"Jan 1, 1970 12:00:00 AM\"", Date.class); +// assertEquals(0, obj.getTime()); +// GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() +// .build(); +// obj = JsonIterator.deserialize(config, "\"Jan 1, 1970 12:00:00 AM\"", Date.class); +// assertEquals(0, obj.getTime()); +// } finally { +// TimeZone.setDefault(orig); +// } +// } + + public void test_setDateFormat_format() { + TimeZone orig = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + Gson gson = new GsonBuilder().setDateFormat("EEE, MMM d, yyyy hh:mm:ss a z").create(); + Date obj = gson.fromJson("\"Thu, Jan 1, 1970 12:00:00 AM UTC\"", Date.class); + assertEquals(0, obj.getTime()); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setDateFormat("EEE, MMM d, yyyy hh:mm:ss a z") + .build(); + obj = JsonIterator.deserialize(config, "\"Thu, Jan 1, 1970 12:00:00 AM UTC\"", Date.class); + assertEquals(0, obj.getTime()); + } finally { + TimeZone.setDefault(orig); + } + } + + public static class TestObject3 { + public String field1; + } + + public void test_setFieldNamingStrategy() { + FieldNamingStrategy fieldNamingStrategy = new FieldNamingStrategy() { + @Override + public String translateName(Field f) { + return "_" + f.getName(); + } + }; + Gson gson = new GsonBuilder() + .setFieldNamingStrategy(fieldNamingStrategy) + .create(); + TestObject3 obj = gson.fromJson("{\"_field1\":\"hello\"}", TestObject3.class); + assertEquals("hello", obj.field1); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setFieldNamingStrategy(fieldNamingStrategy) + .build(); + obj = JsonIterator.deserialize(config, "{\"_field1\":\"hello\"}", TestObject3.class); + assertEquals("hello", obj.field1); + } + + public void test_setFieldNamingPolicy() { + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) + .create(); + TestObject3 obj = gson.fromJson("{\"Field1\":\"hello\"}", TestObject3.class); + assertEquals("hello", obj.field1); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) + .build(); + obj = JsonIterator.deserialize(config, "{\"Field1\":\"hello\"}", TestObject3.class); + assertEquals("hello", obj.field1); + } + + public static class TestObject5 { + @Since(3.0) + public String field1 = ""; + @Until(1.0) + public String field2 = ""; + @Since(2.0) + public String field3 = ""; + @Until(2.0) + public String field4 = ""; + } + + public void test_setVersion() { + Gson gson = new GsonBuilder() + .setVersion(2.0) + .create(); + TestObject5 obj = gson.fromJson("{\"field1\":\"field1\",\"field2\":\"field2\",\"field3\":\"field3\",\"field4\":\"field4\"}", + TestObject5.class); + assertEquals("", obj.field1); + assertEquals("", obj.field2); + assertEquals("field3", obj.field3); + assertEquals("", obj.field4); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setVersion(2.0) + .build(); + obj = JsonIterator.deserialize(config, "{\"field1\":\"field1\",\"field2\":\"field2\",\"field3\":\"field3\",\"field4\":\"field4\"}", + TestObject5.class); + assertEquals("", obj.field1); + assertEquals("", obj.field2); + assertEquals("field3", obj.field3); + assertEquals("", obj.field4); + } + + public void test_addDeserializationExclusionStrategy() { + ExclusionStrategy exclusionStrategy = new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return !f.getName().equals("field3"); + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } + }; + Gson gson = new GsonBuilder() + .addDeserializationExclusionStrategy(exclusionStrategy) + .create(); + TestObject5 obj = gson.fromJson("{\"field1\":\"field1\",\"field2\":\"field2\",\"field3\":\"field3\",\"field4\":\"field4\"}", + TestObject5.class); + assertEquals("", obj.field1); + assertEquals("", obj.field2); + assertEquals("field3", obj.field3); + assertEquals("", obj.field4); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .addDeserializationExclusionStrategy(exclusionStrategy) + .build(); + obj = JsonIterator.deserialize(config, "{\"field1\":\"field1\",\"field2\":\"field2\",\"field3\":\"field3\",\"field4\":\"field4\"}", + TestObject5.class); + assertEquals("", obj.field1); + assertEquals("", obj.field2); + assertEquals("field3", obj.field3); + assertEquals("", obj.field4); + } + + public void test_int_as_string() { + Gson gson = new Gson(); + String str = gson.fromJson("1.1", String.class); + assertEquals("1.1", str); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder().build(); + str = JsonIterator.deserialize(config, "1", String.class); + assertEquals("1", str); + } + + public void test_bool_as_string() { + Gson gson = new Gson(); + String str = gson.fromJson("true", String.class); + assertEquals("true", str); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder().build(); + str = JsonIterator.deserialize(config, "true", String.class); + assertEquals("true", str); + } + + public static class TestObject6 { + public boolean field; + } + + public void test_null_as_boolean() { + Gson gson = new Gson(); + TestObject6 obj = gson.fromJson("{\"field\":null}", TestObject6.class); + assertFalse(obj.field); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder().build(); + obj = JsonIterator.deserialize(config, "{\"field\":null}", TestObject6.class); + assertFalse(obj.field); + } + + public static class TestObject7 { + public long field; + } + + public void test_null_as_long() { + Gson gson = new Gson(); + TestObject7 obj = gson.fromJson("{\"field\":null}", TestObject7.class); + assertEquals(0, obj.field); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder().build(); + obj = JsonIterator.deserialize(config, "{\"field\":null}", TestObject7.class); + assertEquals(0, obj.field); + } + + public static class TestObject8 { + public int field; + } + + public void test_null_as_int() { + Gson gson = new Gson(); + TestObject8 obj = gson.fromJson("{\"field\":null}", TestObject8.class); + assertEquals(0, obj.field); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder().build(); + obj = JsonIterator.deserialize(config, "{\"field\":null}", TestObject8.class); + assertEquals(0, obj.field); + } + + public static class TestObject9 { + public float field; + } + + public void test_null_as_float() { + Gson gson = new Gson(); + TestObject9 obj = gson.fromJson("{\"field\":null}", TestObject9.class); + assertEquals(0.0f, obj.field); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder().build(); + obj = JsonIterator.deserialize(config, "{\"field\":null}", TestObject9.class); + assertEquals(0.0f, obj.field); + } + + public static class TestObject10 { + public double field; + } + + public void test_null_as_double() { + Gson gson = new Gson(); + TestObject10 obj = gson.fromJson("{\"field\":null}", TestObject10.class); + assertEquals(0.0d, obj.field); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder().build(); + obj = JsonIterator.deserialize(config, "{\"field\":null}", TestObject10.class); + assertEquals(0.0d, obj.field); + } +} diff --git a/src/test/java/com/jsoniter/TestIO.java b/src/test/java/com/jsoniter/TestIO.java index 8a781309..99d99dea 100644 --- a/src/test/java/com/jsoniter/TestIO.java +++ b/src/test/java/com/jsoniter/TestIO.java @@ -1,25 +1,33 @@ package com.jsoniter; +import com.jsoniter.spi.JsonException; import junit.framework.TestCase; -import org.junit.experimental.categories.Category; import java.io.ByteArrayInputStream; import java.io.IOException; -@Category(AllTests.StreamingCategory.class) +@org.junit.experimental.categories.Category(StreamingCategory.class) public class TestIO extends TestCase { public void test_read_byte() throws IOException { JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream("1".getBytes()), 4096); assertEquals('1', IterImpl.readByte(iter)); - assertEquals(0, IterImpl.readByte(iter)); + try { + IterImpl.readByte(iter); + fail(); + } catch (JsonException e) { + } } public void test_read_bytes() throws IOException { JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream("12".getBytes()), 4096); assertEquals('1', IterImpl.readByte(iter)); assertEquals('2', IterImpl.readByte(iter)); - assertEquals(0, IterImpl.readByte(iter)); + try { + IterImpl.readByte(iter); + fail(); + } catch (JsonException e) { + } } public void test_unread_byte() throws IOException { diff --git a/src/test/java/com/jsoniter/TestInteger.java b/src/test/java/com/jsoniter/TestInteger.java new file mode 100644 index 00000000..589dae02 --- /dev/null +++ b/src/test/java/com/jsoniter/TestInteger.java @@ -0,0 +1,212 @@ +package com.jsoniter; + +import com.jsoniter.spi.JsonException; +import junit.framework.TestCase; +import org.junit.experimental.categories.Category; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +//import java.math.BigDecimal; +//import java.math.BigInteger; + +public class TestInteger extends TestCase { + + static { +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY); + } + + private boolean isStreaming; + + public void test_char() throws IOException { + Character c = JsonIterator.deserialize("50", Character.class); + assertEquals(50, (int) c); + } + + public void test_positive_negative_int() throws IOException { + assertEquals(0, parseInt("0")); + assertEquals(4321, parseInt("4321")); + assertEquals(54321, parseInt("54321")); + assertEquals(654321, parseInt("654321")); + assertEquals(7654321, parseInt("7654321")); + assertEquals(87654321, parseInt("87654321")); + assertEquals(987654321, parseInt("987654321")); + assertEquals(2147483647, parseInt("2147483647")); + assertEquals(-4321, parseInt("-4321")); + assertEquals(-2147483648, parseInt("-2147483648")); + } + + public void test_positive_negative_long() throws IOException { + assertEquals(0L, parseLong("0")); + assertEquals(4321L, parseLong("4321")); + assertEquals(54321L, parseLong("54321")); + assertEquals(654321L, parseLong("654321")); + assertEquals(7654321L, parseLong("7654321")); + assertEquals(87654321L, parseLong("87654321")); + assertEquals(987654321L, parseLong("987654321")); + assertEquals(9223372036854775807L, parseLong("9223372036854775807")); + assertEquals(-4321L, parseLong("-4321")); + assertEquals(-9223372036854775808L, parseLong("-9223372036854775808")); + } + + public void test_max_min_int() throws IOException { + assertEquals(Integer.MAX_VALUE, parseInt(Integer.toString(Integer.MAX_VALUE))); + assertEquals(Integer.MAX_VALUE - 1, parseInt(Integer.toString(Integer.MAX_VALUE - 1))); + assertEquals(Integer.MIN_VALUE + 1, parseInt(Integer.toString(Integer.MIN_VALUE + 1))); + assertEquals(Integer.MIN_VALUE, parseInt(Integer.toString(Integer.MIN_VALUE))); + } + + public void test_max_min_long() throws IOException { + assertEquals(Long.MAX_VALUE, parseLong(Long.toString(Long.MAX_VALUE))); + assertEquals(Long.MAX_VALUE - 1, parseLong(Long.toString(Long.MAX_VALUE - 1))); + assertEquals(Long.MIN_VALUE + 1, parseLong(Long.toString(Long.MIN_VALUE + 1))); + assertEquals(Long.MIN_VALUE, parseLong(Long.toString(Long.MIN_VALUE))); + } + + public void test_large_number() throws IOException { + try { + JsonIterator.deserialize("2147483648", Integer.class); + fail(); + } catch (JsonException e) { + } + for (int i = 300000000; i < 2000000000; i += 10000000) { + try { + JsonIterator.deserialize(i + "0", Integer.class); + fail(); + } catch (JsonException e) { + } + try { + JsonIterator.deserialize(-i + "0", Integer.class); + fail(); + } catch (JsonException e) { + } + } + try { + JsonIterator.deserialize("9223372036854775808", Long.class); + fail(); + } catch (JsonException e) { + } + for (long i = 1000000000000000000L; i < 9000000000000000000L; i += 100000000000000000L) { + try { + JsonIterator.deserialize(i + "0", Long.class); + fail(); + } catch (JsonException e) { + } + try { + JsonIterator.deserialize(-i + "0", Long.class); + fail(); + } catch (JsonException e) { + } + } + } + + public void test_byte() throws IOException { + Byte val = JsonIterator.deserialize("120", Byte.class); + assertEquals(Byte.valueOf((byte) 120), val); + byte[] vals = JsonIterator.deserialize("[120]", byte[].class); + assertEquals((byte) 120, vals[0]); + } + + @Category(StreamingCategory.class) + public void test_streaming() throws IOException { + isStreaming = true; + test_positive_negative_int(); + test_positive_negative_long(); + test_max_min_int(); + test_max_min_long(); + test_large_number(); + } + + public void test_leading_zero() throws IOException { + assertEquals(Integer.valueOf(0), JsonIterator.deserialize("0", int.class)); + assertEquals(Long.valueOf(0), JsonIterator.deserialize("0", long.class)); + try { + JsonIterator.deserialize("01", int.class); + fail(); + } catch (JsonException e) { + } + try { + JsonIterator.deserialize("02147483647", int.class); + fail(); + } catch (JsonException e) { + } + try { + JsonIterator.deserialize("01", long.class); + fail(); + } catch (JsonException e) { + } + try { + JsonIterator.deserialize("09223372036854775807", long.class); + fail(); + } catch (JsonException e) { + } +/* FIXME if we should fail on parsing of leading zeroes for other numbers + try { + JsonIterator.deserialize("01", double.class); + fail(); + } catch (JsonException e) { + } + try { + JsonIterator.deserialize("01", float.class); + fail(); + } catch (JsonException e) { + } + try { + JsonIterator.deserialize("01", BigInteger.class); + fail(); + } catch (JsonException e) { + } + try { + JsonIterator.deserialize("01", BigDecimal.class); + fail(); + } catch (JsonException e) { + } +*/ + } + + public void test_max_int() throws IOException { + int[] ints = JsonIterator.deserialize("[2147483647,-2147483648]", int[].class); + assertEquals(Integer.MAX_VALUE, ints[0]); + assertEquals(Integer.MIN_VALUE, ints[1]); + } + + private int parseInt(String input) throws IOException { + if (isStreaming) { + JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream(input.getBytes()), 2); + return iter.readInt(); + } else { + JsonIterator iter = JsonIterator.parse(input); + int v = iter.readInt(); + assertEquals(input.length(), iter.head); // iterator head should point on next non-parsed byte + return v; + } + } + + private long parseLong(String input) throws IOException { + if (isStreaming) { + JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream(input.getBytes()), 2); + return iter.readLong(); + } else { + JsonIterator iter = JsonIterator.parse(input); + long v = iter.readLong(); + assertEquals(input.length(), iter.head); // iterator head should point on next non-parsed byte + return v; + } + } + + public void testBigInteger() { + BigInteger number = JsonIterator.deserialize("100", BigInteger.class); + assertEquals(new BigInteger("100"), number); + } + + public void testChooseInteger() { + Object number = JsonIterator.deserialize("100", Object.class); + assertEquals(100, number); + } + + public void testChooseLong() { + Object number = JsonIterator.deserialize(Long.valueOf(Long.MAX_VALUE).toString(), Object.class); + assertEquals(Long.MAX_VALUE, number); + } +} diff --git a/src/test/java/com/jsoniter/TestJackson.java b/src/test/java/com/jsoniter/TestJackson.java new file mode 100644 index 00000000..7f95617e --- /dev/null +++ b/src/test/java/com/jsoniter/TestJackson.java @@ -0,0 +1,73 @@ +package com.jsoniter; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jsoniter.extra.JacksonCompatibilityMode; +import junit.framework.TestCase; + +import java.io.IOException; + +public class TestJackson extends TestCase { + + static { +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); + } + + private ObjectMapper objectMapper; + + public void setUp() { + objectMapper = new ObjectMapper(); + } + + public static class TestObject1 { + private int _id; + private String _name; + + @JsonAnySetter + public void setProperties(String key, Object value) { + if (key.equals("name")) { + _name = (String) value; + } else if (key.equals("id")) { + _id = ((Number) value).intValue(); + } + } + } + + public void test_JsonAnySetter() throws IOException { + TestObject1 obj = objectMapper.readValue("{\"name\":\"hello\",\"id\":100}", TestObject1.class); + assertEquals("hello", obj._name); + assertEquals(100, obj._id); + obj = JsonIterator.deserialize(new JacksonCompatibilityMode.Builder().build(), + "{\"name\":\"hello\",\"id\":100}", TestObject1.class); + assertEquals("hello", obj._name); + assertEquals(100, obj._id); + } + + public static class TestObject2 { + @JsonProperty("field-1") + public String field1; + } + + public void test_JsonProperty() throws IOException { + TestObject2 obj = objectMapper.readValue("{\"field-1\":\"hello\"}", TestObject2.class); + assertEquals("hello", obj.field1); + obj = JsonIterator.deserialize(new JacksonCompatibilityMode.Builder().build(), + "{\"field-1\":\"hello\"}", TestObject2.class); + assertEquals("hello", obj.field1); + } + + public static class TestObject3 { + @JsonIgnore + public String field1; + } + + public void test_JsonIgnore() throws IOException { + TestObject3 obj = objectMapper.readValue("{\"field1\":\"hello\"}", TestObject3.class); + assertNull(obj.field1); + obj = JsonIterator.deserialize(new JacksonCompatibilityMode.Builder().build(), + "{\"field1\":\"hello\"}", TestObject3.class); + assertNull(obj.field1); + } +} diff --git a/src/test/java/com/jsoniter/TestMap.java b/src/test/java/com/jsoniter/TestMap.java new file mode 100644 index 00000000..487b646d --- /dev/null +++ b/src/test/java/com/jsoniter/TestMap.java @@ -0,0 +1,83 @@ +package com.jsoniter; + +import com.jsoniter.extra.GsonCompatibilityMode; +import com.jsoniter.spi.Decoder; +import com.jsoniter.spi.JsoniterSpi; +import com.jsoniter.spi.TypeLiteral; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class TestMap extends TestCase { + + static { +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); + } + + public void test_object_key() throws IOException { + Map map = JsonIterator.deserialize("{\"中文\":null}", new TypeLiteral>() { + }); + assertEquals(new HashMap() {{ + put("中文", null); + }}, map); + } + + public void test_string_key() throws IOException { + Map map = JsonIterator.deserialize("{\"中文\":null}", new TypeLiteral>() { + }); + assertEquals(new HashMap() {{ + put("中文", null); + }}, map); + } + + public void test_integer_key() throws IOException { + Map map = JsonIterator.deserialize("{\"100\":null}", new TypeLiteral>() { + }); + assertEquals(new HashMap() {{ + put(100, null); + }}, map); + } + + public static enum EnumKey { + KeyA, KeyB + } + + public void test_enum_key() { + Map map = JsonIterator.deserialize("{\"KeyA\":null}", new TypeLiteral>() { + }); + assertEquals(new HashMap() {{ + put(EnumKey.KeyA, null); + }}, map); + } + + public static class TestObject1 { + public int Field; + } + + public void test_MapKeyCodec() { + JsoniterSpi.registerMapKeyDecoder(TestObject1.class, new Decoder() { + @Override + public Object decode(JsonIterator iter) throws IOException { + TestObject1 obj = new TestObject1(); + obj.Field = Integer.valueOf(iter.readString()); + return obj; + } + }); + Map map = JsonIterator.deserialize("{\"100\":null}", new TypeLiteral>() { + }); + ArrayList keys = new ArrayList(map.keySet()); + assertEquals(1, keys.size()); + assertEquals(100, keys.get(0).Field); + // in new config + map = JsonIterator.deserialize( + new GsonCompatibilityMode.Builder().build(), + "{\"100\":null}", new TypeLiteral>() { + }); + keys = new ArrayList(map.keySet()); + assertEquals(1, keys.size()); + assertEquals(100, keys.get(0).Field); + } +} diff --git a/src/test/java/com/jsoniter/TestNested.java b/src/test/java/com/jsoniter/TestNested.java index 7bdbc81f..4c60813d 100644 --- a/src/test/java/com/jsoniter/TestNested.java +++ b/src/test/java/com/jsoniter/TestNested.java @@ -25,4 +25,48 @@ public void test_array_of_objects() throws IOException { Any any = iter.readAny(); assertEquals("22", any.toString(1, "field2")); } + + public void test_get_all_array_elements_via_any() throws IOException { + Any any = JsonIterator.deserialize(" [ { \"bar\": 1 }, {\"bar\": 3} ]"); + Any result = any.get('*', "bar"); + assertEquals("[ 1, 3]", result.toString()); + any = Any.rewrap(any.asList()); // make it not lazy + result = any.get('*', "bar"); + assertEquals("[ 1, 3]", result.toString()); + } + + public void skip_get_all_object_values_via_any() throws IOException { + Any any = JsonIterator.deserialize("{\"field1\":[1,2],\"field2\":[3,4]}"); + Any result = any.get('*', 1); + assertEquals("{\"field1\":2,\"field2\":4}", result.toString()); + any = Any.rewrap(any.asMap()); // make it not lazy + result = any.get('*', 1); + assertEquals("{\"field1\":2,\"field2\":4}", result.toString()); + } + + public void test_get_all_with_some_invalid_path() throws IOException { + Any any = JsonIterator.deserialize(" [ { \"bar\": 1 }, {\"foo\": 3} ]"); + Any result = any.get('*', "bar"); + assertEquals("[ 1]", result.toString()); + any = Any.rewrap(any.asList()); // make it not lazy + result = any.get('*', "bar"); + assertEquals("[ 1]", result.toString()); + any = JsonIterator.deserialize("{\"field1\":[1,2],\"field2\":[3]}"); + result = any.get('*', 1); + assertEquals("{\"field1\":2}", result.toString()); + any = Any.rewrap(any.asMap()); // make it not lazy + result = any.get('*', 1); + assertEquals("{\"field1\":2}", result.toString()); + } + + public static class TestObject3 { + public com.jsoniter.output.TestNested.TestObject3 reference; + } + + public void test_recursive_class() { + // recursive reference will not be supported + // however recursive structure is supported + com.jsoniter.output.TestNested.TestObject3 obj = new com.jsoniter.output.TestNested.TestObject3(); + assertNull(JsonIterator.deserialize("{\"reference\":null}", TestObject3.class).reference); + } } diff --git a/src/test/java/com/jsoniter/TestNull.java b/src/test/java/com/jsoniter/TestNull.java new file mode 100644 index 00000000..56763039 --- /dev/null +++ b/src/test/java/com/jsoniter/TestNull.java @@ -0,0 +1,114 @@ +package com.jsoniter; + +import com.jsoniter.any.Any; +import com.jsoniter.spi.DecodingMode; +import junit.framework.TestCase; + +import java.math.BigDecimal; +import java.math.BigInteger; + +public class TestNull extends TestCase { + + static { +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY); + } + + public static class TestObject1 { + public Boolean field; + } + + public void test_null_as_Boolean() { + TestObject1 val = JsonIterator.deserialize("{\"field\":null}", TestObject1.class); + assertNull(val.field); + } + + public static class TestObject2 { + public Float field; + } + + public void test_null_as_Float() { + TestObject2 val = JsonIterator.deserialize("{\"field\":null}", TestObject2.class); + assertNull(val.field); + } + + public static class TestObject3 { + public Double field; + } + + public void test_null_as_Double() { + TestObject3 val = JsonIterator.deserialize("{\"field\":null}", TestObject3.class); + assertNull(val.field); + } + + public static class TestObject4 { + public Byte field; + } + + public void test_null_as_Byte() { + TestObject4 val = JsonIterator.deserialize("{\"field\":null}", TestObject4.class); + assertNull(val.field); + } + + public static class TestObject5 { + public Character field; + } + + public void test_null_as_Character() { + TestObject5 val = JsonIterator.deserialize("{\"field\":null}", TestObject5.class); + assertNull(val.field); + } + + public static class TestObject6 { + public Short field; + } + + public void test_null_as_Short() { + TestObject6 val = JsonIterator.deserialize("{\"field\":null}", TestObject6.class); + assertNull(val.field); + } + + public static class TestObject7 { + public Integer field; + } + + public void test_null_as_Integer() { + TestObject7 val = JsonIterator.deserialize("{\"field\":null}", TestObject7.class); + assertNull(val.field); + } + + public static class TestObject8 { + public Long field; + } + + public void test_null_as_Long() { + TestObject8 val = JsonIterator.deserialize("{\"field\":null}", TestObject8.class); + assertNull(val.field); + } + + public static class TestObject9 { + public BigDecimal field; + } + + public void test_null_as_BigDecimal() { + TestObject9 val = JsonIterator.deserialize("{\"field\":null}", TestObject9.class); + assertNull(val.field); + } + + public static class TestObject10 { + public BigInteger field; + } + + public void test_null_as_BigInteger() { + TestObject10 val = JsonIterator.deserialize("{\"field\":null}", TestObject10.class); + assertNull(val.field); + } + + public static class TestObject11 { + public Any field; + } + + public void test_null_as_Any() { + TestObject11 val = JsonIterator.deserialize("{\"field\":null}", TestObject11.class); + assertNull(val.field.object()); + } +} diff --git a/src/test/java/com/jsoniter/TestObject.java b/src/test/java/com/jsoniter/TestObject.java index 41031a9b..357e4472 100644 --- a/src/test/java/com/jsoniter/TestObject.java +++ b/src/test/java/com/jsoniter/TestObject.java @@ -1,17 +1,23 @@ package com.jsoniter; +import com.jsoniter.annotation.JsonProperty; import com.jsoniter.any.Any; +import com.jsoniter.fuzzy.MaybeEmptyArrayDecoder; +import com.jsoniter.spi.DecodingMode; import com.jsoniter.spi.EmptyExtension; +import com.jsoniter.spi.JsonException; import com.jsoniter.spi.JsoniterSpi; import junit.framework.TestCase; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Map; public class TestObject extends TestCase { static { -// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY); } public static class EmptyClass { @@ -30,7 +36,7 @@ public void test_empty_object() throws IOException { assertNull(simpleObj.field1); iter.reset(iter.buf); Object obj = iter.read(Object.class); - assertEquals(0, ((Map)obj).size()); + assertEquals(0, ((Map) obj).size()); iter.reset(iter.buf); Any any = iter.readAny(); assertEquals(0, any.size()); @@ -48,10 +54,13 @@ public void test_one_field() throws IOException { iter.reset(iter.buf); Any any = iter.readAny(); assertEquals("hello", any.toString("field1")); - assertNull(any.get("field2")); + assertEquals(ValueType.INVALID, any.get("field2").valueType()); + iter.reset(iter.buf); + assertEquals("hello", ((Map) iter.read()).get("field1")); } public void test_two_fields() throws IOException { +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); JsonIterator iter = JsonIterator.parse("{ 'field1' : 'hello' , 'field2': 'world' }".replace('\'', '"')); assertEquals("field1", iter.readObject()); assertEquals("hello", iter.readString()); @@ -64,9 +73,19 @@ public void test_two_fields() throws IOException { assertEquals("world", simpleObj.field2); iter.reset(iter.buf); Any any = iter.readAny(); - any.require("field1"); assertEquals("hello", any.toString("field1")); assertEquals("world", any.toString("field2")); + iter.reset(iter.buf); + final ArrayList fields = new ArrayList(); + iter.readObjectCB(new JsonIterator.ReadObjectCallback() { + @Override + public boolean handle(JsonIterator iter, String field, Object attachment) throws IOException { + fields.add(field); + iter.skip(); + return true; + } + }, null); + assertEquals(Arrays.asList("field1", "field2"), fields); } public void test_read_null() throws IOException { @@ -147,10 +166,12 @@ public enum MyEnum { WORLD, WOW } + public MyEnum field1; } public void test_enum() throws IOException { +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); TestObject5 obj = JsonIterator.deserialize("{\"field1\":\"HELLO\"}", TestObject5.class); assertEquals(TestObject5.MyEnum.HELLO, obj.field1); try { @@ -163,4 +184,87 @@ public void test_enum() throws IOException { obj = JsonIterator.deserialize("{\"field1\":\"WOW\"}", TestObject5.class); assertEquals(TestObject5.MyEnum.WOW, obj.field1); } + + public static class TestObject6_field1 { + public int a; + } + + public static class TestObject6 { + @JsonProperty(decoder = MaybeEmptyArrayDecoder.class) + public TestObject6_field1 field1; + } + + public void test_maybe_empty_array_field() { + TestObject6 obj = JsonIterator.deserialize("{\"field1\":[]}", TestObject6.class); + assertNull(obj.field1); + obj = JsonIterator.deserialize("{\"field1\":{\"a\":1}}", TestObject6.class); + assertEquals(1, obj.field1.a); + } + + public void test_iterator() { + Any any = JsonIterator.deserialize("{\"field1\":1,\"field2\":2,\"field3\":3}"); + Any.EntryIterator iter = any.entries(); + assertTrue(iter.next()); + assertEquals("field1", iter.key()); + assertEquals(1, iter.value().toInt()); + iter = any.entries(); + assertTrue(iter.next()); + assertEquals("field1", iter.key()); + assertEquals(1, iter.value().toInt()); + assertTrue(iter.next()); + assertEquals("field2", iter.key()); + assertEquals(2, iter.value().toInt()); + assertTrue(iter.next()); + assertEquals("field3", iter.key()); + assertEquals(3, iter.value().toInt()); + assertFalse(iter.next()); + } + + public static class PublicSuper { + public String field1; + } + + private static class PrivateSub extends PublicSuper { + } + + public static class TestObject7 { + public PrivateSub field1; + + public void setFieldXXX(PrivateSub obj) { + } + } + + public void test_private_ref() throws IOException { + TestObject7 obj = JsonIterator.deserialize("{}", TestObject7.class); + assertNull(obj.field1); + } + + public static class TestObject8 { + public String field1; + + @JsonProperty(from = {"field-1"}) + public void setField1(String obj) { + field1 = "!!!" + obj; + } + } + + public void test_setter_is_preferred() throws IOException { + TestObject8 obj = JsonIterator.deserialize("{\"field-1\":\"hello\"}", TestObject8.class); + assertEquals("!!!hello", obj.field1); + } + + public void skip_object_lazy_any_to_string() { + Any any = JsonIterator.deserialize("{\"field1\":1,\"field2\":2,\"field3\":3}"); + any.asMap().put("field4", Any.wrap(4)); + assertEquals("{\"field1\":1,\"field3\":3,\"field2\":2,\"field4\":4}", any.toString()); + } + + public static class TestObject9 { + public int 字段; + } + + public void test_non_ascii_field() { + TestObject9 obj = JsonIterator.deserialize("{\"字段\":100}", TestObject9.class); + assertEquals(100, obj.字段); + } } diff --git a/src/test/java/com/jsoniter/TestOmitValue.java b/src/test/java/com/jsoniter/TestOmitValue.java new file mode 100644 index 00000000..80c11e76 --- /dev/null +++ b/src/test/java/com/jsoniter/TestOmitValue.java @@ -0,0 +1,137 @@ +package com.jsoniter; + +import com.jsoniter.spi.OmitValue.*; +import junit.framework.TestCase; + +public class TestOmitValue extends TestCase { + + public void test_shouldOmitInputPositiveOutputFalse() { + + // Arrange + final ZeroByte objectUnderTest = new ZeroByte(); + final Object val = (byte)1; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse2() { + + // Arrange + final ZeroInt objectUnderTest = new ZeroInt(); + final Object val = 1; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse3() { + + // Arrange + final ZeroLong objectUnderTest = new ZeroLong(); + final Object val = 1L; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputZeroOutputTrue() { + + // Arrange + final ZeroLong objectUnderTest = new ZeroLong(); + final Object val = 0L; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(true, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse4() { + + // Arrange + final ZeroShort objectUnderTest = new ZeroShort(); + final Object val = (short)1; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputTrueOutputFalse() { + + // Arrange + final False objectUnderTest = new False(); + final Object val = true; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputNotNullOutputFalse() { + + // Arrange + final ZeroChar objectUnderTest = new ZeroChar(); + final Object val = '\u0001'; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse5() { + + // Arrange + final ZeroDouble objectUnderTest = new ZeroDouble(); + final Object val = 0x0.0000000000001p-1022 /* 4.94066e-324 */; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputZeroOutputTrue2() { + + // Arrange + final ZeroDouble objectUnderTest = new ZeroDouble(); + final Object val = 0.0; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(true, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse6() { + + // Arrange + final ZeroFloat objectUnderTest = new ZeroFloat(); + final Object val = 0x1p-149f /* 1.4013e-45 */; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } +} diff --git a/src/test/java/com/jsoniter/TestReadAny.java b/src/test/java/com/jsoniter/TestReadAny.java index 9ec7f1ef..0437de9a 100644 --- a/src/test/java/com/jsoniter/TestReadAny.java +++ b/src/test/java/com/jsoniter/TestReadAny.java @@ -1,6 +1,7 @@ package com.jsoniter; import com.jsoniter.any.Any; +import com.jsoniter.spi.JsonException; import junit.framework.TestCase; import org.junit.experimental.categories.Category; @@ -8,8 +9,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; -import java.util.List; public class TestReadAny extends TestCase { @@ -98,10 +99,10 @@ public void test_read_int_as_string() throws IOException { public void test_get() throws IOException { assertEquals("100.5", JsonIterator.deserialize("100.5").get().toString()); assertEquals("100.5", JsonIterator.deserialize("[100.5]").get(0).toString()); - assertNull(JsonIterator.deserialize("null").get(0)); - assertNull(JsonIterator.deserialize("[]").get(0)); - assertNull(JsonIterator.deserialize("[]").get("hello")); - assertNull(JsonIterator.deserialize("{}").get(0)); + assertEquals(ValueType.INVALID, JsonIterator.deserialize("null").get(0).valueType()); + assertEquals(ValueType.INVALID, JsonIterator.deserialize("[]").get(0).valueType()); + assertEquals(ValueType.INVALID, JsonIterator.deserialize("[]").get("hello").valueType()); + assertEquals(ValueType.INVALID, JsonIterator.deserialize("{}").get(0).valueType()); } public void test_read_long() throws IOException { @@ -157,26 +158,36 @@ public void test_read_multiple_field() throws IOException { } public void test_require_path() throws IOException { - assertNotNull(JsonIterator.deserialize("null").require()); + assertNotNull(JsonIterator.deserialize("null").get()); try { - JsonIterator.deserialize("[]").require(0); + JsonIterator.deserialize("[]").get(0).object(); } catch (JsonException e) { System.out.println(e); } try { - JsonIterator.deserialize("{}").require("hello"); + Any.rewrap(new ArrayList()).get(0).object(); + } catch (JsonException e) { + System.out.println(e); + } + try { + JsonIterator.deserialize("{}").get("hello").object(); + } catch (JsonException e) { + System.out.println(e); + } + try { + Any.rewrap(new HashMap()).get("hello").object(); } catch (JsonException e) { System.out.println(e); } } - @Category(AllTests.StreamingCategory.class) + @Category(StreamingCategory.class) public void test_read_any_in_streaming() throws IOException { - assertEquals(2, JsonIterator.parse(new ByteArrayInputStream("[1,2,3,4,5]" .getBytes()), 2).readAny().toInt(1)); - assertEquals(1, JsonIterator.parse(new ByteArrayInputStream("{\"field1\": 1}" .getBytes()), 2).readAny().size()); - JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream("[1,2,[3, 4],5]" .getBytes()), 2); + assertEquals(2, JsonIterator.parse(new ByteArrayInputStream("[1,2,3,4,5]".getBytes()), 2).readAny().toInt(1)); + assertEquals(1, JsonIterator.parse(new ByteArrayInputStream("{\"field1\": 1}".getBytes()), 2).readAny().size()); + JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream("[1,2,[3, 4],5]".getBytes()), 2); ArrayList elements = new ArrayList(); - while(iter.readArray()) { + while (iter.readArray()) { elements.add(iter.readAny()); } assertEquals("[3, 4]", elements.get(2).toString()); diff --git a/src/test/java/com/jsoniter/TestReflection.java b/src/test/java/com/jsoniter/TestReflection.java deleted file mode 100644 index 203a92ea..00000000 --- a/src/test/java/com/jsoniter/TestReflection.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jsoniter; - -import com.jsoniter.spi.JsoniterSpi; -import junit.framework.TestCase; - -import java.io.IOException; - -public class TestReflection extends TestCase { - - public static class PackageLocal { - String field; - } - - public void test_package_local() throws IOException { - JsoniterSpi.registerTypeDecoder(PackageLocal.class, ReflectionDecoderFactory.create(PackageLocal.class)); - JsonIterator iter = JsonIterator.parse("{'field': 'hello'}".replace('\'', '"')); - PackageLocal obj = iter.read(PackageLocal.class); - assertEquals("hello", obj.field); - } - - public static class Inherited extends PackageLocal { - } - - public void test_inherited() throws IOException { - JsoniterSpi.registerTypeDecoder(Inherited.class, ReflectionDecoderFactory.create(Inherited.class)); - JsonIterator iter = JsonIterator.parse("{'field': 'hello'}".replace('\'', '"')); - Inherited obj = iter.read(Inherited.class); - assertEquals("hello", obj.field); - } - - public static class ObjectWithInt { - private int field; - } - - public void test_int_field() throws IOException { - JsoniterSpi.registerTypeDecoder(ObjectWithInt.class, ReflectionDecoderFactory.create(ObjectWithInt.class)); - JsonIterator iter = JsonIterator.parse("{'field': 100}".replace('\'', '"')); - ObjectWithInt obj = iter.read(ObjectWithInt.class); - assertEquals(100, obj.field); - } -} diff --git a/src/test/java/com/jsoniter/TestSkip.java b/src/test/java/com/jsoniter/TestSkip.java index 3e7444ba..eb796fe3 100644 --- a/src/test/java/com/jsoniter/TestSkip.java +++ b/src/test/java/com/jsoniter/TestSkip.java @@ -1,7 +1,10 @@ package com.jsoniter; +import com.jsoniter.spi.JsonException; import junit.framework.TestCase; +import org.junit.experimental.categories.Category; +import java.io.ByteArrayInputStream; import java.io.IOException; public class TestSkip extends TestCase { @@ -24,6 +27,27 @@ public void test_skip_string() throws IOException { assertFalse(iter.readArray()); } + @Category(StreamingCategory.class) + public void test_skip_string_streaming() throws IOException { + JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream("\"hello".getBytes()), 2); + try { + iter.skip(); + fail(); + } catch (JsonException e) { + } + iter = JsonIterator.parse(new ByteArrayInputStream("\"hello\"".getBytes()), 2); + iter.skip(); + iter = JsonIterator.parse(new ByteArrayInputStream("\"hello\"1".getBytes()), 2); + iter.skip(); + assertEquals(1, iter.readInt()); + iter = JsonIterator.parse(new ByteArrayInputStream("\"h\\\"ello\"1".getBytes()), 3); + iter.skip(); + assertEquals(1, iter.readInt()); + iter = JsonIterator.parse(new ByteArrayInputStream("\"\\\\\"1".getBytes()), 3); + iter.skip(); + assertEquals(1, iter.readInt()); + } + public void test_skip_object() throws IOException { JsonIterator iter = JsonIterator.parse("[{'hello': {'world': 'a'}},2]".replace('\'', '"')); assertTrue(iter.readArray()); diff --git a/src/test/java/com/jsoniter/TestSlice.java b/src/test/java/com/jsoniter/TestSlice.java index 9648eb09..668372c6 100644 --- a/src/test/java/com/jsoniter/TestSlice.java +++ b/src/test/java/com/jsoniter/TestSlice.java @@ -1,5 +1,6 @@ package com.jsoniter; +import com.jsoniter.spi.Slice; import junit.framework.TestCase; import java.util.HashMap; @@ -18,4 +19,32 @@ public void test_hashcode() { assertEquals("hello", map.get(Slice.make("hello"))); assertEquals("world", map.get(Slice.make("world"))); } + + public void test_equalsInputNotNullOutputFalse2() { + + // Arrange + final byte[] byteArray = {(byte)2, (byte)1}; + final Slice objectUnderTest = new Slice(byteArray, 0, 1073741825); + final byte[] byteArray1 = {(byte)0}; + final Slice o = new Slice(byteArray1, 0, 1073741825); + + // Act + final boolean retval = objectUnderTest.equals(o); + + // Assert result + assertEquals(false, retval); + } + + public void test_equalsInputNotNullOutputFalse() { + + // Arrange + final Slice objectUnderTest = new Slice(null, 0, -2147483646); + final Slice o = new Slice(null, 0, 2); + + // Act + final boolean retval = objectUnderTest.equals(o); + + // Assert result + assertEquals(false, retval); + } } diff --git a/src/test/java/com/jsoniter/TestSpiPropertyDecoder.java b/src/test/java/com/jsoniter/TestSpiPropertyDecoder.java new file mode 100644 index 00000000..63924920 --- /dev/null +++ b/src/test/java/com/jsoniter/TestSpiPropertyDecoder.java @@ -0,0 +1,45 @@ +package com.jsoniter; + +import com.jsoniter.spi.Decoder; +import com.jsoniter.spi.JsoniterSpi; +import com.jsoniter.spi.TypeLiteral; +import junit.framework.TestCase; + +import java.io.IOException; + +public class TestSpiPropertyDecoder extends TestCase { + + static { +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); + } + + public static class TestObject1 { + public String field; + } + + public void test_PropertyDecoder() { + JsoniterSpi.registerPropertyDecoder(TestObject1.class, "field", new Decoder() { + @Override + public Object decode(JsonIterator iter) throws IOException { + iter.skip(); + return "hello"; + } + }); + TestObject1 obj = JsonIterator.deserialize("{\"field\":100}", TestObject1.class); + assertEquals("hello", obj.field); + } + + public void test_PropertyDecoder_for_type_literal() { + TypeLiteral> typeLiteral = new TypeLiteral>() { + }; + JsoniterSpi.registerPropertyDecoder(typeLiteral, "field", new Decoder() { + @Override + public Object decode(JsonIterator iter) throws IOException { + iter.skip(); + return "world"; + } + }); + TestObject1 obj = JsonIterator.deserialize("{\"field\":100}", typeLiteral); + assertEquals("world", obj.field); + } +} diff --git a/src/test/java/com/jsoniter/TestSpiTypeDecoder.java b/src/test/java/com/jsoniter/TestSpiTypeDecoder.java new file mode 100644 index 00000000..3127e181 --- /dev/null +++ b/src/test/java/com/jsoniter/TestSpiTypeDecoder.java @@ -0,0 +1,125 @@ +package com.jsoniter; + +import com.jsoniter.spi.Decoder; +import com.jsoniter.spi.JsonException; +import com.jsoniter.spi.JsoniterSpi; +import com.jsoniter.spi.TypeLiteral; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +public class TestSpiTypeDecoder extends TestCase { + + static { +// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); + } + + public static class TestObject1 { + public int field1; + } + + public void test_TypeDecoder() throws IOException { + JsoniterSpi.registerTypeDecoder(TestObject1.class, new Decoder() { + @Override + public Object decode(JsonIterator iter) throws IOException { + iter.skip(); + TestObject1 obj = new TestObject1(); + obj.field1 = 101; + return obj; + } + }); + TestObject1 obj = JsonIterator.deserialize( + "{'field1': 100}".replace('\'', '"'), TestObject1.class); + assertEquals(101, obj.field1); + } + + public void test_TypeDecoder_for_generics() throws IOException { + TypeLiteral> typeLiteral = new TypeLiteral>() { + }; + JsoniterSpi.registerTypeDecoder(typeLiteral, new Decoder() { + @Override + public Object decode(JsonIterator iter) throws IOException { + iter.skip(); + TestObject1 obj = new TestObject1(); + obj.field1 = 101; + return Arrays.asList(obj); + } + }); + List objs = JsonIterator.deserialize( + "{'field1': 100}".replace('\'', '"'), typeLiteral); + assertEquals(101, objs.get(0).field1); + } + + public static class MyDate { + Date date; + } + + static { + JsoniterSpi.registerTypeDecoder(MyDate.class, new Decoder() { + @Override + public Object decode(final JsonIterator iter) throws IOException { + return new MyDate() {{ + date = new Date(iter.readLong()); + }}; + } + }); + } + + public void test_direct() throws IOException { + JsonIterator iter = JsonIterator.parse("1481365190000"); + MyDate date = iter.read(MyDate.class); + assertEquals(1481365190000L, date.date.getTime()); + } + + public static class FieldWithMyDate { + public MyDate field; + } + + public void test_as_field_type() throws IOException { + JsonIterator iter = JsonIterator.parse("{'field': 1481365190000}".replace('\'', '"')); + FieldWithMyDate obj = iter.read(FieldWithMyDate.class); + assertEquals(1481365190000L, obj.field.date.getTime()); + } + + public void test_as_array_element() throws IOException { + JsonIterator iter = JsonIterator.parse("[1481365190000]"); + MyDate[] dates = iter.read(MyDate[].class); + assertEquals(1481365190000L, dates[0].date.getTime()); + } + + public static class MyList { + public List list; + } + + public void test_list_or_single_element() { + final TypeLiteral> listOfString = new TypeLiteral>() { + }; + JsoniterSpi.registerTypeDecoder(MyList.class, new Decoder() { + @Override + public Object decode(JsonIterator iter) throws IOException { + ValueType valueType = iter.whatIsNext(); + MyList myList = new MyList(); + switch (valueType) { + case ARRAY: + myList.list = iter.read(listOfString); + return myList; + case STRING: + myList.list = new ArrayList(); + myList.list.add(iter.readString()); + return myList; + default: + throw new JsonException("unexpected input"); + } + } + }); + MyList list1 = JsonIterator.deserialize("\"hello\"", MyList.class); + assertEquals("hello", list1.list.get(0)); + MyList list2 = JsonIterator.deserialize("[\"hello\",\"world\"]", MyList.class); + assertEquals("hello", list2.list.get(0)); + assertEquals("world", list2.list.get(1)); + } +} diff --git a/src/test/java/com/jsoniter/TestString.java b/src/test/java/com/jsoniter/TestString.java index f9f9aa6f..07238a9d 100644 --- a/src/test/java/com/jsoniter/TestString.java +++ b/src/test/java/com/jsoniter/TestString.java @@ -1,11 +1,10 @@ package com.jsoniter; +import com.jsoniter.spi.JsonException; import junit.framework.TestCase; import org.junit.experimental.categories.Category; import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; public class TestString extends TestCase { @@ -14,42 +13,69 @@ public class TestString extends TestCase { // JsonIterator.enableStreamingSupport(); } - public void test_string() throws IOException { + public void test_ascii_string() throws IOException { JsonIterator iter = JsonIterator.parse("'hello''world'".replace('\'', '"')); assertEquals("hello", iter.readString()); assertEquals("world", iter.readString()); iter = JsonIterator.parse("'hello''world'".replace('\'', '"')); - assertEquals("hello", iter.readString()); - assertEquals("world", iter.readString()); + assertEquals("hello", iter.readStringAsSlice().toString()); + assertEquals("world", iter.readStringAsSlice().toString()); + } + + public void test_ascii_string_with_escape() throws IOException { + JsonIterator iter = JsonIterator.parse("'he\\tllo'".replace('\'', '"')); + assertEquals("he\tllo", iter.readString()); + } + + public void test_utf8_string() throws IOException { + JsonIterator iter = JsonIterator.parse("'中文'".replace('\'', '"')); + assertEquals("中文", iter.readString()); + } + + public void test_incomplete_escape() throws IOException { + JsonIterator iter = JsonIterator.parse("\"\\"); + try { + iter.readString(); + fail(); + } catch (JsonException e) { + } + } + + public void test_surrogate() throws IOException { + JsonIterator iter = JsonIterator.parse("\"\ud83d\udc4a\""); + assertEquals("\ud83d\udc4a", iter.readString()); } - public void test_base64() throws IOException { - JsonIterator iter = JsonIterator.parse("'YWJj'".replace('\'', '"')); - assertEquals("abc", new String(iter.readBase64())); + public void test_larger_than_buffer() throws IOException { + JsonIterator iter = JsonIterator.parse("'0123456789012345678901234567890123'".replace('\'', '"')); + assertEquals("0123456789012345678901234567890123", iter.readString()); } - @Category(AllTests.StreamingCategory.class) + @org.junit.experimental.categories.Category(StreamingCategory.class) public void test_string_across_buffer() throws IOException { JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream("'hello''world'".replace('\'', '"').getBytes()), 2); assertEquals("hello", iter.readString()); assertEquals("world", iter.readString()); + iter = JsonIterator.parse(new ByteArrayInputStream("'hello''world'".replace('\'', '"').getBytes()), 2); + assertEquals("hello", iter.readStringAsSlice().toString()); + assertEquals("world", iter.readStringAsSlice().toString()); } - @Category(AllTests.StreamingCategory.class) + @org.junit.experimental.categories.Category(StreamingCategory.class) public void test_utf8() throws IOException { byte[] bytes = {'"', (byte) 0xe4, (byte) 0xb8, (byte) 0xad, (byte) 0xe6, (byte) 0x96, (byte) 0x87, '"'}; JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream(bytes), 2); assertEquals("中文", iter.readString()); } - @Category(AllTests.StreamingCategory.class) + @org.junit.experimental.categories.Category(StreamingCategory.class) public void test_normal_escape() throws IOException { byte[] bytes = {'"', (byte) '\\', (byte) 't', '"'}; JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream(bytes), 2); assertEquals("\t", iter.readString()); } - @Category(AllTests.StreamingCategory.class) + @org.junit.experimental.categories.Category(StreamingCategory.class) public void test_unicode_escape() throws IOException { byte[] bytes = {'"', (byte) '\\', (byte) 'u', (byte) '4', (byte) 'e', (byte) '2', (byte) 'd', '"'}; JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream(bytes), 2); @@ -63,9 +89,40 @@ public void test_null_string() throws IOException { public void test_incomplete_string() throws IOException { try { - JsonIterator.parse("\"abc").read(); + JsonIterator.deserialize("\"abc", String.class); fail(); } catch (JsonException e) { } } + + public void test_invalid_string() throws IOException { + for (String str : new String[]{ + "\"\\x0008\"", + "\"\\u000Z\"", + "\"\\u000\"", + "\"\\u00\"", + "\"\\u0\"", + "\"\\\"", + "\"\\udd1e\"", + "\"\\ud834\"", + "\"\\ud834\\x\"", + "\"\\ud834\\ud834\"", + }) { + try {JsonIterator.deserialize(str, String.class); + } catch (JsonException e) { + } catch (IndexOutOfBoundsException e) { + } + } + } + + public void test_long_string() throws IOException { + JsonIterator iter = JsonIterator.parse("\"[\\\"LL\\\",\\\"MM\\\\\\/LW\\\",\\\"JY\\\",\\\"S\\\",\\\"C\\\",\\\"IN\\\",\\\"ME \\\\\\/ LE\\\"]\""); + assertEquals("[\"LL\",\"MM\\/LW\",\"JY\",\"S\",\"C\",\"IN\",\"ME \\/ LE\"]", iter.readString()); + } + + @Category(StreamingCategory.class) + public void test_long_string_in_streaming() throws IOException { + JsonIterator iter = JsonIterator.parse(new ByteArrayInputStream("\"[\\\"LL\\\",\\\"MM\\\\\\/LW\\\",\\\"JY\\\",\\\"S\\\",\\\"C\\\",\\\"IN\\\",\\\"ME \\\\\\/ LE\\\"]\"".getBytes()), 2); + assertEquals("[\"LL\",\"MM\\/LW\",\"JY\",\"S\",\"C\",\"IN\",\"ME \\/ LE\"]", iter.readString()); + } } diff --git a/src/test/java/com/jsoniter/any/TestArray.java b/src/test/java/com/jsoniter/any/TestArray.java new file mode 100644 index 00000000..9744fec8 --- /dev/null +++ b/src/test/java/com/jsoniter/any/TestArray.java @@ -0,0 +1,80 @@ +package com.jsoniter.any; + +import com.jsoniter.JsonIterator; +import com.jsoniter.output.EncodingMode; +import com.jsoniter.output.JsonStream; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; + +public class TestArray extends TestCase { + public void test_size() { + Any any = Any.wrap(new int[]{1, 2, 3}); + assertEquals(3, any.size()); + } + + public void test_to_boolean() { + Any any = Any.wrap(new int[0]); + assertFalse(any.toBoolean()); + any = Any.wrap(new Object[]{"hello", 1}); + assertTrue(any.toBoolean()); + } + + public void test_to_int() { + Any any = Any.wrap(new int[0]); + assertEquals(0, any.toInt()); + any = Any.wrap(new Object[]{"hello", 1}); + assertEquals(2, any.toInt()); + } + + public void test_get() { + Any any = Any.wrap(new Object[]{"hello", 1}); + assertEquals("hello", any.get(0).toString()); + } + + public void test_get_from_nested() { + Any any = Any.wrap(new Object[]{new String[]{"hello"}, new String[]{"world"}}); + assertEquals("hello", any.get(0, 0).toString()); + assertEquals("[\"hello\",\"world\"]", any.get('*', 0).toString()); + } + + public void test_iterator() { + Any any = Any.wrap(new long[]{1, 2, 3}); + ArrayList list = new ArrayList(); + for (Any element : any) { + list.add(element.toInt()); + } + assertEquals(Arrays.asList(1, 2, 3), list); + } + + public void test_to_string() { + assertEquals("[1,2,3]", Any.wrap(new long[]{1, 2, 3}).toString()); + Any any = Any.wrap(new long[]{1, 2, 3}); + any.asList().add(Any.wrap(4)); + assertEquals("[1,2,3,4]", any.toString()); + } + + public void test_fill_partial_then_iterate() { + Any obj = JsonIterator.deserialize("[1,2,3]"); + assertEquals(1, obj.get(0).toInt()); + Iterator iter = obj.iterator(); + assertEquals(1, iter.next().toInt()); + assertEquals(2, iter.next().toInt()); + assertEquals(3, iter.next().toInt()); + assertFalse(iter.hasNext()); + } + + public void test_equals_and_hashcode() { + Any obj1 = JsonIterator.deserialize("[1,2,3]"); + Any obj2 = JsonIterator.deserialize("[1, 2, 3]"); + assertEquals(obj1, obj2); + assertEquals(obj1.hashCode(), obj2.hashCode()); + } + + public void test_null() { + Any x = JsonIterator.deserialize("{\"test\":null}"); + assertFalse(x.get("test").iterator().hasNext()); + } +} diff --git a/src/test/java/com/jsoniter/any/TestList.java b/src/test/java/com/jsoniter/any/TestList.java new file mode 100644 index 00000000..0dcd5799 --- /dev/null +++ b/src/test/java/com/jsoniter/any/TestList.java @@ -0,0 +1,63 @@ +package com.jsoniter.any; + +import com.jsoniter.JsonIterator; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; + +public class TestList extends TestCase { + public void test_size() { + Any any = Any.wrap(Arrays.asList(1, 2, 3)); + assertEquals(3, any.size()); + } + + public void test_to_boolean() { + Any any = Any.wrap(Collections.emptyList()); + assertFalse(any.toBoolean()); + any = Any.wrap(Arrays.asList("hello", 1)); + assertTrue(any.toBoolean()); + } + + public void test_to_int() { + Any any = Any.wrap(Collections.emptyList()); + assertEquals(0, any.toInt()); + any = Any.wrap(Arrays.asList("hello", 1)); + assertEquals(2, any.toInt()); + } + + public void test_get() { + Any any = Any.wrap(Arrays.asList("hello", 1)); + assertEquals("hello", any.get(0).toString()); + } + + public void test_get_from_nested() { + Any any = Any.wrap(Arrays.asList(Collections.singletonList("hello"), Collections.singletonList("world"))); + assertEquals("hello", any.get(0, 0).toString()); + assertEquals("[\"hello\",\"world\"]", any.get('*', 0).toString()); + } + + public void test_iterator() { + Any any = Any.wrap(Arrays.asList(1, 2, 3)); + ArrayList list = new ArrayList(); + for (Any element : any) { + list.add(element.toInt()); + } + assertEquals(Arrays.asList(1, 2, 3), list); + } + + public void test_to_string() { + assertEquals("[1,2,3]", Any.wrap(Arrays.asList(1, 2, 3)).toString()); + Any any = Any.wrap(Arrays.asList(1, 2, 3)); + any.asList().add(Any.wrap(4)); + assertEquals("[1,2,3,4]", any.toString()); + } + + public void test_for_each() { + Any a = JsonIterator.deserialize("[]"); + Iterator iter = a.iterator(); + assertFalse(iter.hasNext()); + } +} diff --git a/src/test/java/com/jsoniter/any/TestLong.java b/src/test/java/com/jsoniter/any/TestLong.java new file mode 100644 index 00000000..247dbe8f --- /dev/null +++ b/src/test/java/com/jsoniter/any/TestLong.java @@ -0,0 +1,28 @@ +package com.jsoniter.any; + +import com.jsoniter.spi.JsonException; +import junit.framework.TestCase; + +public class TestLong extends TestCase { + public void test_to_string_should_trim() { + Any any = Any.lazyLong(" 1000".getBytes(), 0, " 1000".length()); + assertEquals("1000", any.toString()); + } + + public void test_should_fail_with_leading_zero() { + byte[] bytes = "01".getBytes(); + Any any = Any.lazyLong(bytes, 0, bytes.length); + try { + any.toLong(); + fail("This should fail."); + } catch (JsonException e) { + + } + } + + public void test_should_work_with_zero() { + byte[] bytes = "0".getBytes(); + Any any = Any.lazyLong(bytes, 0, bytes.length); + assertEquals(0L, any.toLong()); + } +} diff --git a/src/test/java/com/jsoniter/any/TestMap.java b/src/test/java/com/jsoniter/any/TestMap.java new file mode 100644 index 00000000..e5bc31f6 --- /dev/null +++ b/src/test/java/com/jsoniter/any/TestMap.java @@ -0,0 +1,64 @@ +package com.jsoniter.any; + +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + +public class TestMap extends TestCase { + + public void test_size() { + Any any = Any.wrap(mapOf("hello", 1, "world", 2)); + assertEquals(2, any.size()); + } + + public void test_to_boolean() { + Any any = Any.wrap(mapOf()); + assertFalse(any.toBoolean()); + any = Any.wrap(mapOf("hello", 1)); + assertTrue(any.toBoolean()); + } + + public void test_to_int() { + Any any = Any.wrap(mapOf()); + assertEquals(0, any.toInt()); + any = Any.wrap(mapOf("hello", 1)); + assertEquals(1, any.toInt()); + } + + public void test_get() { + Any any = Any.wrap(mapOf("hello", 1, "world", 2)); + assertEquals(2, any.get("world").toInt()); + } + + public void test_get_from_nested() { + Any any = Any.wrap(mapOf("a", mapOf("b", "c"), "d", mapOf("e", "f"))); + assertEquals("c", any.get("a", "b").toString()); + assertEquals("{\"a\":\"c\"}", any.get('*', "b").toString()); + } + + public void test_iterator() { + Any any = Any.wrap(mapOf("hello", 1, "world", 2)); + Any.EntryIterator iter = any.entries(); + HashMap map = new HashMap(); + while (iter.next()) { + map.put(iter.key(), iter.value().toInt()); + } + assertEquals(mapOf("hello", 1, "world", 2), map); + } + + public void test_to_string() { + assertEquals("{\"world\":2,\"hello\":1}", Any.wrap(mapOf("hello", 1, "world", 2)).toString()); + Any any = Any.wrap(mapOf("hello", 1, "world", 2)); + any.asMap().put("abc", Any.wrap(3)); + assertEquals("{\"world\":2,\"abc\":3,\"hello\":1}", any.toString()); + } + + private static Map mapOf(Object... args) { + HashMap map = new HashMap(); + for (int i = 0; i < args.length; i += 2) { + map.put((String) args[i], args[i + 1]); + } + return map; + } +} diff --git a/src/test/java/com/jsoniter/extra/TestBase64.java b/src/test/java/com/jsoniter/extra/TestBase64.java new file mode 100644 index 00000000..2a67f3c2 --- /dev/null +++ b/src/test/java/com/jsoniter/extra/TestBase64.java @@ -0,0 +1,19 @@ +package com.jsoniter.extra; + +import com.jsoniter.JsonIterator; +import com.jsoniter.output.JsonStream; +import junit.framework.TestCase; + +public class TestBase64 extends TestCase { + static { + Base64Support.enable(); + } + + public void test_encode() { + assertEquals("\"YWJj\"", JsonStream.serialize("abc".getBytes())); + } + + public void test_decode() { + assertEquals("abc", new String(JsonIterator.deserialize("\"YWJj\"", byte[].class))); + } +} diff --git a/src/test/java/com/jsoniter/extra/TestBase64Float.java b/src/test/java/com/jsoniter/extra/TestBase64Float.java new file mode 100644 index 00000000..44f98190 --- /dev/null +++ b/src/test/java/com/jsoniter/extra/TestBase64Float.java @@ -0,0 +1,32 @@ +package com.jsoniter.extra; + +import com.jsoniter.JsonIterator; +import com.jsoniter.output.JsonStream; +import junit.framework.TestCase; + +public class TestBase64Float extends TestCase { + + static { + Base64FloatSupport.enableEncodersAndDecoders(); + } + + public void test_Double() { + String json = JsonStream.serialize(0.123456789d); + assertEquals(0.123456789d, JsonIterator.deserialize(json, Double.class)); + } + + public void test_double() { + String json = JsonStream.serialize(new double[]{0.123456789d}); + assertEquals(0.123456789d, JsonIterator.deserialize(json, double[].class)[0]); + } + + public void test_Float() { + String json = JsonStream.serialize(0.12345678f); + assertEquals(0.12345678f, JsonIterator.deserialize(json, Float.class)); + } + + public void test_float() { + String json = JsonStream.serialize(new float[]{0.12345678f}); + assertEquals(0.12345678f, JsonIterator.deserialize(json, float[].class)[0]); + } +} diff --git a/src/test/java/com/jsoniter/datetime/TestJdkDatetime.java b/src/test/java/com/jsoniter/extra/TestJdkDatetime.java similarity index 89% rename from src/test/java/com/jsoniter/datetime/TestJdkDatetime.java rename to src/test/java/com/jsoniter/extra/TestJdkDatetime.java index ced946d2..4f57a859 100644 --- a/src/test/java/com/jsoniter/datetime/TestJdkDatetime.java +++ b/src/test/java/com/jsoniter/extra/TestJdkDatetime.java @@ -1,4 +1,4 @@ -package com.jsoniter.datetime; +package com.jsoniter.extra; import com.jsoniter.JsonIterator; import com.jsoniter.output.JsonStream; @@ -9,7 +9,7 @@ public class TestJdkDatetime extends TestCase { - public void test() { + public void skip_test() { JdkDatetimeSupport.enable("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); assertEquals("\"1970-01-01T08:00:00.000+0800\"", JsonStream.serialize(new Date(0))); Date obj = JsonIterator.deserialize("\"1970-01-01T08:00:00.000+0800\"", Date.class); diff --git a/src/test/java/com/jsoniter/extra/TestNamingStrategy.java b/src/test/java/com/jsoniter/extra/TestNamingStrategy.java new file mode 100644 index 00000000..33a2fdcd --- /dev/null +++ b/src/test/java/com/jsoniter/extra/TestNamingStrategy.java @@ -0,0 +1,16 @@ +package com.jsoniter.extra; + +import com.jsoniter.output.JsonStream; +import junit.framework.TestCase; + +public class TestNamingStrategy extends TestCase { + public static class TestObject1 { + public String helloWorld; + } + public void test() { + NamingStrategySupport.enable(NamingStrategySupport.SNAKE_CASE); + TestObject1 obj = new TestObject1(); + obj.helloWorld = "!!!"; + assertEquals("{\"hello_world\":\"!!!\"}", JsonStream.serialize(obj)); + } +} diff --git a/src/test/java/com/jsoniter/extra/TestPreciseFloat.java b/src/test/java/com/jsoniter/extra/TestPreciseFloat.java new file mode 100644 index 00000000..d63082ae --- /dev/null +++ b/src/test/java/com/jsoniter/extra/TestPreciseFloat.java @@ -0,0 +1,31 @@ +package com.jsoniter.extra; + +import com.jsoniter.output.JsonStream; +import junit.framework.TestCase; + +public class TestPreciseFloat extends TestCase { + static { + PreciseFloatSupport.enable(); + } + + public void test_direct_encode() { + assertEquals("0.123456789", JsonStream.serialize(0.123456789d)); + assertEquals("0.12345678", JsonStream.serialize(0.12345678f)); + } + + public static class TestObject1 { + public Double field1; + public double field2; + public Float field3; + public float field4; + } + + public void test_indirect_encode() { + TestObject1 obj = new TestObject1(); + obj.field1 = 0.12345678d; + obj.field2 = 0.12345678d; + obj.field3 = 0.12345678f; + obj.field4 = 0.12345678f; + assertEquals("{\"field1\":0.12345678,\"field2\":0.12345678,\"field3\":0.12345678,\"field4\":0.12345678}", JsonStream.serialize(obj)); + } +} diff --git a/src/test/java/com/jsoniter/output/TestAnnotation.java b/src/test/java/com/jsoniter/output/TestAnnotation.java deleted file mode 100644 index 6472f01b..00000000 --- a/src/test/java/com/jsoniter/output/TestAnnotation.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.jsoniter.output; - -import com.jsoniter.annotation.JsonIgnore; -import com.jsoniter.annotation.JsonProperty; -import com.jsoniter.annotation.JsonUnwrapper; -import com.jsoniter.annotation.JsoniterAnnotationSupport; -import com.jsoniter.spi.Encoder; -import junit.framework.TestCase; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -public class TestAnnotation extends TestCase { - static { - JsoniterAnnotationSupport.enable(); -// JsonStream.setMode(EncodingMode.DYNAMIC_MODE); - } - - private ByteArrayOutputStream baos; - private JsonStream stream; - - public void setUp() { - baos = new ByteArrayOutputStream(); - stream = new JsonStream(baos, 4096); - } - - public static class TestObject1 { - @JsonProperty(to = {"field-1"}) - public String field1; - } - - public void test_property() throws IOException { - TestObject1 obj = new TestObject1(); - obj.field1 = "hello"; - stream.writeVal(obj); - stream.close(); - assertEquals("{\"field-1\":\"hello\"}", baos.toString()); - } - - public static class TestObject2 { - @JsonProperty(encoder = Encoder.StringIntEncoder.class) - public int field1; - } - - public void test_encoder() throws IOException { - TestObject2 obj = new TestObject2(); - obj.field1 = 100; - stream.writeVal(obj); - stream.close(); - assertEquals("{\"field1\":\"100\"}", baos.toString()); - } - - public static class TestObject3 { - @JsonIgnore - public int field1; - } - - public void test_ignore() throws IOException { - TestObject3 obj = new TestObject3(); - obj.field1 = 100; - stream.writeVal(obj); - stream.close(); - assertEquals("{}", baos.toString()); - } - - public static class TestObject4 { - public int field1; - - public int getField1() { - return field1; - } - } - - public void test_name_conflict() throws IOException { - TestObject4 obj = new TestObject4(); - stream.writeVal(obj); - stream.close(); - assertEquals("{\"field1\":0}", baos.toString()); - } - - public static class TestObject5 { - @JsonUnwrapper - public void unwrap(JsonStream stream) throws IOException { - stream.writeObjectField("hello"); - stream.writeVal("world"); - } - } - - public void test_unwrapper() throws IOException { - TestObject5 obj = new TestObject5(); - stream.writeVal(obj); - stream.close(); - assertEquals("{\"hello\":\"world\"}", baos.toString()); - } -} diff --git a/src/test/java/com/jsoniter/output/TestAnnotationJsonIgnore.java b/src/test/java/com/jsoniter/output/TestAnnotationJsonIgnore.java new file mode 100644 index 00000000..a7fab10d --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestAnnotationJsonIgnore.java @@ -0,0 +1,46 @@ +package com.jsoniter.output; + +import com.jsoniter.annotation.JsonIgnore; +import junit.framework.TestCase; + +import java.io.IOException; + +public class TestAnnotationJsonIgnore extends TestCase { + + public static class TestObject1 { + @JsonIgnore + public int field1; + } + + public void test_ignore() throws IOException { + TestObject1 obj = new TestObject1(); + obj.field1 = 100; + assertEquals("{}", JsonStream.serialize(obj)); + } + + public static class TestObject2 { + @JsonIgnore(ignoreEncoding = false) + public int field1; + } + + public void test_ignore_decoding_only() throws IOException { + TestObject2 obj = new TestObject2(); + obj.field1 = 100; + assertEquals("{\"field1\":100}", JsonStream.serialize(obj)); + } + + public static class TestPrivateVariables { + @JsonIgnore + private String field1; + + public String getField1() { + return field1; + } + } + + public void test_private_serialize() throws IOException { + TestPrivateVariables obj = new TestPrivateVariables(); + obj.field1 = "hello"; + assertEquals("{}", JsonStream.serialize(obj)); + } +} diff --git a/src/test/java/com/jsoniter/output/TestAnnotationJsonProperty.java b/src/test/java/com/jsoniter/output/TestAnnotationJsonProperty.java new file mode 100644 index 00000000..495941df --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestAnnotationJsonProperty.java @@ -0,0 +1,71 @@ +package com.jsoniter.output; + +import com.jsoniter.annotation.JsonProperty; +import com.jsoniter.spi.Encoder; +import junit.framework.TestCase; + +import java.io.IOException; + +public class TestAnnotationJsonProperty extends TestCase { + + static { +// JsonStream.setMode(EncodingMode.DYNAMIC_MODE); + } + + public static class TestObject1 { + @JsonProperty(to = {"field-1"}) + public String field1; + } + + public void test_property() throws IOException { + TestObject1 obj = new TestObject1(); + obj.field1 = "hello"; + String output = JsonStream.serialize(obj); + assertEquals("{\"field-1\":\"hello\"}", output); + } + + + public static class TestObject2 { + @JsonProperty(encoder = Encoder.StringIntEncoder.class) + public int field1; + } + + public void test_encoder() throws IOException { + TestObject2 obj = new TestObject2(); + obj.field1 = 100; + String output = JsonStream.serialize(obj); + assertEquals("{\"field1\":\"100\"}", output); + } + + public static class TestObject3 { + public String field1 = "hello"; + + @JsonProperty("field-1") + public String getField1() { + return field1; + } + } + + public void test_getter() throws IOException { + String output = JsonStream.serialize(new TestObject3()); + assertEquals("{\"field-1\":\"hello\"}", output); + } + + public static class TestObject4 { + private String field1 = "hello"; + + @JsonProperty("field-1") + public String getField1() { + return field1; + } + + public void setField1(String field1) { + this.field1 = field1; + } + } + + public void test_getter_and_setter() throws IOException { + String output = JsonStream.serialize(new TestObject4()); + assertEquals("{\"field-1\":\"hello\"}", output); + } +} diff --git a/src/test/java/com/jsoniter/output/TestAnnotationJsonUnwrapper.java b/src/test/java/com/jsoniter/output/TestAnnotationJsonUnwrapper.java new file mode 100644 index 00000000..9ad9f66c --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestAnnotationJsonUnwrapper.java @@ -0,0 +1,51 @@ +package com.jsoniter.output; + +import com.jsoniter.annotation.JsonUnwrapper; +import junit.framework.TestCase; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class TestAnnotationJsonUnwrapper extends TestCase { + + private ByteArrayOutputStream baos; + private JsonStream stream; + + public void setUp() { + baos = new ByteArrayOutputStream(); + stream = new JsonStream(baos, 4096); + } + + public static class TestObject1 { + @JsonUnwrapper + public void unwrap(JsonStream stream) throws IOException { + stream.writeObjectField("hello"); + stream.writeVal("world"); + } + } + + public void test_unwrapper() throws IOException { + TestObject1 obj = new TestObject1(); + stream.writeVal(obj); + stream.close(); + assertEquals("{\"hello\":\"world\"}", baos.toString()); + } + + public static class TestObject2 { + @JsonUnwrapper + public Map getProperties() { + HashMap properties = new HashMap(); + properties.put(100, "hello"); + return properties; + } + } + + public void test_unwrapper_with_map() throws IOException { + TestObject2 obj = new TestObject2(); + stream.writeVal(obj); + stream.close(); + assertEquals("{\"100\":\"hello\"}", baos.toString()); + } +} diff --git a/src/test/java/com/jsoniter/output/TestAny.java b/src/test/java/com/jsoniter/output/TestAny.java index f7d01023..5e4ce3e8 100644 --- a/src/test/java/com/jsoniter/output/TestAny.java +++ b/src/test/java/com/jsoniter/output/TestAny.java @@ -2,13 +2,23 @@ import com.jsoniter.ValueType; import com.jsoniter.any.*; +import com.jsoniter.spi.JsonException; import junit.framework.TestCase; +import org.junit.Rule; +import org.junit.rules.ExpectedException; import java.util.Arrays; import java.util.HashMap; public class TestAny extends TestCase { + @Rule + public final ExpectedException exception = ExpectedException.none(); + + static { +// JsonStream.setMode(EncodingMode.DYNAMIC_MODE); + } + public void test_int() { Any any = Any.wrap(100); assertEquals(ValueType.NUMBER, any.valueType()); @@ -116,7 +126,13 @@ public void test_array() { assertEquals("[1,2,3]", any.toString()); } - public void test_map() { + public void test_not_found() { + Any any = Any.wrap(new int[]{1, 2, 3}); + exception.expect(JsonException.class); + any.get("not", "found", "path"); + } + + public void skip_map() { HashMap val = new HashMap(); val.put("hello", 1); val.put("world", "!!"); @@ -133,7 +149,7 @@ public static class MyClass { public Any field2; } - public void test_my_class() { + public void skip_my_class() { MyClass val = new MyClass(); val.field1 = "hello"; val.field2 = Any.wrap(new long[]{1, 2}); diff --git a/src/test/java/com/jsoniter/output/TestArray.java b/src/test/java/com/jsoniter/output/TestArray.java index 579e4783..80626f1e 100644 --- a/src/test/java/com/jsoniter/output/TestArray.java +++ b/src/test/java/com/jsoniter/output/TestArray.java @@ -1,12 +1,12 @@ package com.jsoniter.output; +import com.jsoniter.spi.Config; import com.jsoniter.spi.TypeLiteral; import junit.framework.TestCase; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class TestArray extends TestCase { @@ -23,7 +23,7 @@ public void setUp() { } public void test_gen_array() throws IOException { - stream.writeVal(new String[] {"hello", "world"}); + stream.writeVal(new String[]{"hello", "world"}); stream.close(); assertEquals("['hello','world']".replace('\'', '"'), baos.toString()); } @@ -32,7 +32,8 @@ public void test_collection() throws IOException { ArrayList list = new ArrayList(); list.add("hello"); list.add("world"); - stream.writeVal(new TypeLiteral>(){}, list); + stream.writeVal(new TypeLiteral>() { + }, list); stream.close(); assertEquals("['hello','world']".replace('\'', '"'), baos.toString()); } @@ -53,7 +54,8 @@ public void test_empty_array() throws IOException { } public void test_null_array() throws IOException { - stream.writeVal(new TypeLiteral(){}, null); + stream.writeVal(new TypeLiteral() { + }, null); stream.close(); assertEquals("null".replace('\'', '"'), baos.toString()); } @@ -65,8 +67,86 @@ public void test_empty_collection() throws IOException { } public void test_null_collection() throws IOException { - stream.writeVal(new TypeLiteral(){}, null); + stream.writeVal(new TypeLiteral() { + }, null); stream.close(); assertEquals("null".replace('\'', '"'), baos.toString()); } + + public static class TestObject1 { + public List field1; + } + + public void test_list_of_objects() throws IOException { + TestObject1 obj = new TestObject1(); + obj.field1 = Arrays.asList("a", "b"); + stream.writeVal(new TypeLiteral>() { + }, Arrays.asList(obj)); + stream.close(); + assertEquals("[{\"field1\":[\"a\",\"b\"]}]", baos.toString()); + } + + public void test_array_of_null() throws IOException { + stream.writeVal(new TestObject1[1]); + stream.close(); + assertEquals("[null]", baos.toString()); + } + + public void test_list_of_null() throws IOException { + TestObject1 obj = new TestObject1(); + obj.field1 = Arrays.asList("a", "b"); + ArrayList list = new ArrayList(); + list.add(null); + stream.writeVal(new TypeLiteral>() { + }, list); + stream.close(); + assertEquals("[null]", baos.toString()); + } + + public void test_hash_set() throws IOException { + assertEquals("[]", JsonStream.serialize(new HashSet())); + HashSet set = new HashSet(); + set.add(1); + assertEquals("[1]", JsonStream.serialize(set)); + } + + public void test_arrays_as_list() throws IOException { + assertEquals("[1,2,3]", JsonStream.serialize(Arrays.asList(1, 2, 3))); + } + + public void test_default_empty_collection() throws IOException { + assertEquals("[]", JsonStream.serialize(Collections.emptySet())); + } + + public void test_indention() { + Config cfg = new Config.Builder() + .encodingMode(EncodingMode.REFLECTION_MODE) + .indentionStep(2) + .build(); + assertEquals("[\n" + + " 1,\n" + + " 2\n" + + "]", JsonStream.serialize(cfg, new int[]{1, 2})); + cfg = new Config.Builder() + .encodingMode(EncodingMode.DYNAMIC_MODE) + .indentionStep(2) + .build(); + assertEquals("[\n" + + " 1,\n" + + " 2\n" + + "]", JsonStream.serialize(cfg, new int[]{1, 2})); + } + + public void test_indention_with_empty_array() { + Config cfg = new Config.Builder() + .encodingMode(EncodingMode.REFLECTION_MODE) + .indentionStep(2) + .build(); + assertEquals("[]", JsonStream.serialize(cfg, new int[]{})); + cfg = new Config.Builder() + .encodingMode(EncodingMode.DYNAMIC_MODE) + .indentionStep(2) + .build(); + assertEquals("[]", JsonStream.serialize(cfg, new int[]{})); + } } diff --git a/src/test/java/com/jsoniter/output/TestCollection.java b/src/test/java/com/jsoniter/output/TestCollection.java new file mode 100644 index 00000000..ffe12eae --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestCollection.java @@ -0,0 +1,41 @@ +package com.jsoniter.output; + +import com.jsoniter.spi.Config; +import junit.framework.TestCase; + +import java.util.HashSet; + +public class TestCollection extends TestCase { + + public void test_indention() { + HashSet set = new HashSet(); + set.add(1); + Config cfg = new Config.Builder() + .encodingMode(EncodingMode.REFLECTION_MODE) + .indentionStep(2) + .build(); + assertEquals("[\n" + + " 1\n" + + "]", JsonStream.serialize(cfg, set)); + cfg = new Config.Builder() + .encodingMode(EncodingMode.DYNAMIC_MODE) + .indentionStep(2) + .build(); + assertEquals("[\n" + + " 1\n" + + "]", JsonStream.serialize(cfg, set)); + } + + public void test_indention_with_empty_array() { + Config cfg = new Config.Builder() + .encodingMode(EncodingMode.REFLECTION_MODE) + .indentionStep(2) + .build(); + assertEquals("[]", JsonStream.serialize(cfg, new HashSet())); + cfg = new Config.Builder() + .encodingMode(EncodingMode.DYNAMIC_MODE) + .indentionStep(2) + .build(); + assertEquals("[]", JsonStream.serialize(cfg, new HashSet())); + } +} diff --git a/src/test/java/com/jsoniter/output/TestCustomizeField.java b/src/test/java/com/jsoniter/output/TestCustomizeField.java deleted file mode 100644 index 1b8a03ca..00000000 --- a/src/test/java/com/jsoniter/output/TestCustomizeField.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jsoniter.output; - -import com.jsoniter.any.Any; -import com.jsoniter.spi.Encoder; -import com.jsoniter.spi.JsoniterSpi; -import junit.framework.TestCase; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -public class TestCustomizeField extends TestCase { - - private ByteArrayOutputStream baos; - private JsonStream stream; - - public void setUp() { - baos = new ByteArrayOutputStream(); - stream = new JsonStream(baos, 4096); - } - - public static class TestObject1 { - public String field1; - } - - public void test_customize_field_decoder() throws IOException { - JsoniterSpi.registerPropertyEncoder(TestObject1.class, "field1", new Encoder() { - @Override - public void encode(Object obj, JsonStream stream) throws IOException { - String str = (String) obj; - stream.writeVal(Integer.valueOf(str)); - } - - @Override - public Any wrap(Object obj) { - throw new UnsupportedOperationException(); - } - }); - TestObject1 obj = new TestObject1(); - obj.field1 = "100"; - stream.writeVal(obj); - stream.close(); - assertEquals("{'field1':100}".replace('\'', '"'), baos.toString()); - } -} diff --git a/src/test/java/com/jsoniter/output/TestCustomizeType.java b/src/test/java/com/jsoniter/output/TestCustomizeType.java deleted file mode 100644 index 2ac910fb..00000000 --- a/src/test/java/com/jsoniter/output/TestCustomizeType.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jsoniter.output; - -import com.jsoniter.spi.EmptyEncoder; -import com.jsoniter.spi.JsoniterSpi; -import junit.framework.TestCase; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Date; - -public class TestCustomizeType extends TestCase { - - private ByteArrayOutputStream baos; - private JsonStream stream; - - public void setUp() { - baos = new ByteArrayOutputStream(); - stream = new JsonStream(baos, 4096); - } - - public static class MyDate { - Date date; - } - - public void test() throws IOException { - JsoniterSpi.registerTypeEncoder(MyDate.class, new EmptyEncoder() { - @Override - public void encode(Object obj, JsonStream stream) throws IOException { - MyDate date = (MyDate) obj; - stream.writeVal(date.date.getTime()); - } - }); - MyDate myDate = new MyDate(); - myDate.date = new Date(1481365190000L); - stream.writeVal(myDate); - stream.close(); - assertEquals("1481365190000", baos.toString()); - } -} diff --git a/src/test/java/com/jsoniter/output/TestFloat.java b/src/test/java/com/jsoniter/output/TestFloat.java new file mode 100644 index 00000000..faf72d31 --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestFloat.java @@ -0,0 +1,17 @@ +package com.jsoniter.output; + +import junit.framework.TestCase; + +import java.math.BigDecimal; + +public class TestFloat extends TestCase { + public void testBigDecimal() { + assertEquals("100.1", JsonStream.serialize(new BigDecimal("100.1"))); + } + public void test_infinity() { + assertEquals("\"Infinity\"", JsonStream.serialize(Double.POSITIVE_INFINITY)); + assertEquals("\"Infinity\"", JsonStream.serialize(Float.POSITIVE_INFINITY)); + assertEquals("\"-Infinity\"", JsonStream.serialize(Double.NEGATIVE_INFINITY)); + assertEquals("\"-Infinity\"", JsonStream.serialize(Float.NEGATIVE_INFINITY)); + } +} diff --git a/src/test/java/com/jsoniter/output/TestGenerics.java b/src/test/java/com/jsoniter/output/TestGenerics.java new file mode 100644 index 00000000..ff06d780 --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestGenerics.java @@ -0,0 +1,57 @@ +package com.jsoniter.output; + +import junit.framework.TestCase; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TestGenerics extends TestCase { + static { +// JsonStream.setMode(EncodingMode.DYNAMIC_MODE); + } + + private ByteArrayOutputStream baos; + private JsonStream stream; + + public void setUp() { + baos = new ByteArrayOutputStream(); + stream = new JsonStream(baos, 4096); + } + + public interface TestObject6Interface { + A getHello(); + } + + public static class TestObject6 implements TestObject6Interface { + public Integer getHello() { + return 0; + } + } + + public void test_inherited_getter_is_not_duplicate() throws IOException { + TestObject6 obj = new TestObject6(); + stream.writeVal(obj); + stream.close(); + assertEquals("{\"hello\":0}", baos.toString()); + } + + public static class TestObject7 { + public List field; + public Map field2; + } + + public void test_wildcard() throws IOException { + TestObject7 obj = new TestObject7(); + ArrayList list = new ArrayList(); + list.add(1); + obj.field = list; + HashMap map = new HashMap(); + map.put("hello", 1); + obj.field2 = map; + assertEquals("{\"field\":[1],\"field2\":{\"hello\":1}}", JsonStream.serialize(obj)); + } +} diff --git a/src/test/java/com/jsoniter/output/TestGson.java b/src/test/java/com/jsoniter/output/TestGson.java new file mode 100644 index 00000000..afa78828 --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestGson.java @@ -0,0 +1,297 @@ +package com.jsoniter.output; + +import com.google.gson.*; +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.extra.GsonCompatibilityMode; +import com.jsoniter.spi.JsoniterSpi; +import junit.framework.TestCase; + +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class TestGson extends TestCase { + + public static class TestObject1 { + @SerializedName("field-1") + public String field1; + } + + public void test_SerializedName_on_field() { + Gson gson = new GsonBuilder().create(); + TestObject1 obj = new TestObject1(); + obj.field1 = "hello"; + String output = gson.toJson(obj); + assertEquals("{\"field-1\":\"hello\"}", output); + output = JsonStream.serialize(new GsonCompatibilityMode.Builder().build(), obj); + assertEquals("{\"field-1\":\"hello\"}", output); + } + + public static class TestObject2 { + @Expose(serialize = false) + public String field1; + } + + public void test_Expose() { + Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + TestObject2 obj = new TestObject2(); + obj.field1 = "hello"; + String output = gson.toJson(obj); + assertEquals("{}", output); + + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .excludeFieldsWithoutExposeAnnotation().build(); + output = JsonStream.serialize(config, obj); + assertEquals("{}", output); + } + + public static class TestObject3 { + public String getField1() { + return "hello"; + } + } + + public void test_getter_should_be_ignored() { + Gson gson = new GsonBuilder().create(); + TestObject3 obj = new TestObject3(); + String output = gson.toJson(obj); + assertEquals("{}", output); + output = JsonStream.serialize(new GsonCompatibilityMode.Builder().build(), obj); + assertEquals("{}", output); + } + + public static class TestObject4 { + public String field1; + } + + public void test_excludeFieldsWithoutExposeAnnotation() { + Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + TestObject4 obj = new TestObject4(); + obj.field1 = "hello"; + String output = gson.toJson(obj); + assertEquals("{}", output); + + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .excludeFieldsWithoutExposeAnnotation().build(); + output = JsonStream.serialize(config, obj); + assertEquals("{}", output); + } + + public static class TestObject5 { + public String field1; + public int field2; + } + + public void test_serializeNulls() { + TestObject5 obj = new TestObject5(); + Gson gson = new GsonBuilder().create(); + String output = gson.toJson(obj); + assertEquals("{\"field2\":0}", output); + + gson = new GsonBuilder().serializeNulls().create(); + output = gson.toJson(obj); + assertEquals("{\"field1\":null,\"field2\":0}", output); + + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .build(); + output = JsonStream.serialize(config, obj); + assertEquals("{\"field2\":0}", output); + + config = new GsonCompatibilityMode.Builder() + .serializeNulls().build(); + output = JsonStream.serialize(config, obj); + assertEquals("{\"field1\":null,\"field2\":0}", output); + } + + public void test_setDateFormat_with_style() { + TimeZone orig = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + Gson gson = new GsonBuilder() + .setDateFormat(DateFormat.LONG, DateFormat.LONG) + .create(); + String output = gson.toJson(new Date(0)); + assertEquals("\"January 1, 1970 12:00:00 AM UTC\"", output); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setDateFormat(DateFormat.LONG, DateFormat.LONG) + .build(); + output = JsonStream.serialize(config, new Date(0)); + assertEquals("\"January 1, 1970 12:00:00 AM UTC\"", output); + } finally { + TimeZone.setDefault(orig); + } + } + + public void test_setDateFormat_with_format() { + TimeZone orig = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + Gson gson = new GsonBuilder() + .setDateFormat("EEE, MMM d, yyyy hh:mm:ss a z") + .create(); + String output = gson.toJson(new Date(0)); + assertEquals("\"Thu, Jan 1, 1970 12:00:00 AM UTC\"", output); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setDateFormat("EEE, MMM d, yyyy hh:mm:ss a z") + .build(); + output = JsonStream.serialize(config, new Date(0)); + assertEquals("\"Thu, Jan 1, 1970 12:00:00 AM UTC\"", output); + } finally { + TimeZone.setDefault(orig); + } + } + + public void test_setFieldNamingStrategy() { + FieldNamingStrategy fieldNamingStrategy = new FieldNamingStrategy() { + @Override + public String translateName(Field f) { + return "_" + f.getName(); + } + }; + Gson gson = new GsonBuilder() + .setFieldNamingStrategy(fieldNamingStrategy) + .create(); + TestObject4 obj = new TestObject4(); + obj.field1 = "hello"; + String output = gson.toJson(obj); + assertEquals("{\"_field1\":\"hello\"}", output); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setFieldNamingStrategy(fieldNamingStrategy) + .build(); + output = JsonStream.serialize(config, obj); + assertEquals("{\"_field1\":\"hello\"}", output); + } + + public void test_setFieldNamingPolicy() { + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) + .create(); + TestObject4 obj = new TestObject4(); + obj.field1 = "hello"; + String output = gson.toJson(obj); + assertEquals("{\"Field1\":\"hello\"}", output); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) + .build(); + output = JsonStream.serialize(config, obj); + assertEquals("{\"Field1\":\"hello\"}", output); + } + + public void test_setPrettyPrinting() { + if (JsoniterSpi.getCurrentConfig().encodingMode() != EncodingMode.REFLECTION_MODE) { + return; + } + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + TestObject4 obj = new TestObject4(); + obj.field1 = "hello"; + String output = gson.toJson(obj); + assertEquals("{\n" + + " \"field1\": \"hello\"\n" + + "}", output); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setPrettyPrinting() + .build(); + output = JsonStream.serialize(config, obj); + assertEquals("{\n" + + " \"field1\": \"hello\"\n" + + "}", output); + } + + public void test_disableHtmlEscaping_off() { + Gson gson = new GsonBuilder() + .disableHtmlEscaping() + .create(); + String output = gson.toJson("中文"); + assertEquals("\"中文\"", output); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .disableHtmlEscaping() + .build(); + output = JsonStream.serialize(config, "中文"); + assertEquals("\"中文\"", output); + } + + public void test_disableHtmlEscaping_on() { + Gson gson = new GsonBuilder() + .create(); + String output = gson.toJson(" "); + assertEquals("\"\\u003chtml\\u003e\\u0026nbsp;\\u003c/html\\u003e\"", output); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .build(); + output = JsonStream.serialize(config, " "); + assertEquals("\"\\u003chtml\\u003e\\u0026nbsp;\\u003c/html\\u003e\"", output); + } + + public static class TestObject6 { + @Since(3.0) + public String field1 = "field1"; + @Until(1.0) + public String field2 = "field2"; + @Since(2.0) + public String field3 = "field3"; + @Until(2.0) + public String field4 = "field4"; + } + + public void test_setVersion() { + TestObject6 obj = new TestObject6(); + Gson gson = new GsonBuilder() + .setVersion(2.0) + .create(); + String output = gson.toJson(obj); + assertEquals("{\"field3\":\"field3\"}", output); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .setVersion(2.0) + .build(); + output = JsonStream.serialize(config, obj); + assertEquals("{\"field3\":\"field3\"}", output); + } + + public void test_addSerializationExclusionStrategy() { + TestObject6 obj = new TestObject6(); + ExclusionStrategy exclusionStrategy = new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return !f.getName().equals("field3"); + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } + }; + Gson gson = new GsonBuilder() + .addSerializationExclusionStrategy(exclusionStrategy) + .create(); + String output = gson.toJson(obj); + assertEquals("{\"field3\":\"field3\"}", output); + GsonCompatibilityMode config = new GsonCompatibilityMode.Builder() + .addSerializationExclusionStrategy(exclusionStrategy) + .build(); + output = JsonStream.serialize(config, obj); + assertEquals("{\"field3\":\"field3\"}", output); + } + + + private static class TestObject { + private String test; + } + + public void test_surrogate() { + GsonCompatibilityMode gsonConfig = + new GsonCompatibilityMode.Builder() + .disableHtmlEscaping() + .build(); + + String input = "{\"test\":\"lorem-\uD83D\uDC44\uD83D\uDC40\"}"; + TestObject testObject = new Gson().fromJson(input, TestObject.class); + + System.out.println("Gson: " + new Gson().toJson(testObject)); + System.out.println("jsoniter: " + JsonStream.serialize(gsonConfig, testObject)); + } +} diff --git a/src/test/java/com/jsoniter/output/TestInteger.java b/src/test/java/com/jsoniter/output/TestInteger.java new file mode 100644 index 00000000..64dc1ce9 --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestInteger.java @@ -0,0 +1,11 @@ +package com.jsoniter.output; + +import junit.framework.TestCase; + +import java.math.BigInteger; + +public class TestInteger extends TestCase { + public void testBigInteger() { + assertEquals("100", JsonStream.serialize(new BigInteger("100"))); + } +} diff --git a/src/test/java/com/jsoniter/output/TestJackson.java b/src/test/java/com/jsoniter/output/TestJackson.java new file mode 100644 index 00000000..9d2bd821 --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestJackson.java @@ -0,0 +1,71 @@ +package com.jsoniter.output; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.jsoniter.extra.JacksonCompatibilityMode; +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + +public class TestJackson extends TestCase { + + static { +// JsonStream.setMode(EncodingMode.DYNAMIC_MODE); + } + + private ObjectMapper objectMapper; + + public void setUp() { + objectMapper = new ObjectMapper(); + } + + public static class TestObject1 { + @JsonAnyGetter + public Map getProperties() { + HashMap properties = new HashMap(); + properties.put(100, "hello"); + return properties; + } + } + + public void test_JsonAnyGetter() throws JsonProcessingException { + String output = objectMapper.writeValueAsString(new TestObject1()); + assertEquals("{\"100\":\"hello\"}", output); + output = JsonStream.serialize(new JacksonCompatibilityMode.Builder().build(), new TestObject1()); + assertEquals("{\"100\":\"hello\"}", output); + } + + public static class TestObject2 { + @JsonProperty("field-1") + public String field1; + } + + public void test_JsonProperty() throws JsonProcessingException { + TestObject2 obj = new TestObject2(); + obj.field1 = "hello"; + String output = objectMapper.writeValueAsString(obj); + assertEquals("{\"field-1\":\"hello\"}", output); + output = JsonStream.serialize(new JacksonCompatibilityMode.Builder().build(), obj); + assertEquals("{\"field-1\":\"hello\"}", output); + } + + public static class TestObject3 { + @JsonIgnore + public String field1; + } + + public void test_JsonIgnore() throws JsonProcessingException { + objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + TestObject3 obj = new TestObject3(); + obj.field1 = "hello"; + String output = objectMapper.writeValueAsString(obj); + assertEquals("{}", output); + output = JsonStream.serialize(new JacksonCompatibilityMode.Builder().build(), obj); + assertEquals("{}", output); + } +} diff --git a/src/test/java/com/jsoniter/output/TestList.java b/src/test/java/com/jsoniter/output/TestList.java new file mode 100644 index 00000000..665fcd3f --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestList.java @@ -0,0 +1,42 @@ +package com.jsoniter.output; + +import com.jsoniter.spi.Config; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; + +public class TestList extends TestCase { + + public void test_indention() { + Config cfg = new Config.Builder() + .encodingMode(EncodingMode.REFLECTION_MODE) + .indentionStep(2) + .build(); + assertEquals("[\n" + + " 1,\n" + + " 2\n" + + "]", JsonStream.serialize(cfg, Arrays.asList(1, 2))); + cfg = new Config.Builder() + .encodingMode(EncodingMode.DYNAMIC_MODE) + .indentionStep(2) + .build(); + assertEquals("[\n" + + " 1,\n" + + " 2\n" + + "]", JsonStream.serialize(cfg, Arrays.asList(1, 2))); + } + + public void test_indention_with_empty_array() { + Config cfg = new Config.Builder() + .encodingMode(EncodingMode.REFLECTION_MODE) + .indentionStep(2) + .build(); + assertEquals("[]", JsonStream.serialize(cfg, new ArrayList())); + cfg = new Config.Builder() + .encodingMode(EncodingMode.DYNAMIC_MODE) + .indentionStep(2) + .build(); + assertEquals("[]", JsonStream.serialize(cfg, new ArrayList())); + } +} diff --git a/src/test/java/com/jsoniter/output/TestMap.java b/src/test/java/com/jsoniter/output/TestMap.java index e3b25657..2b91d6f0 100644 --- a/src/test/java/com/jsoniter/output/TestMap.java +++ b/src/test/java/com/jsoniter/output/TestMap.java @@ -1,16 +1,21 @@ package com.jsoniter.output; +import com.jsoniter.spi.Config; +import com.jsoniter.spi.Encoder; +import com.jsoniter.spi.JsoniterSpi; import com.jsoniter.spi.TypeLiteral; import junit.framework.TestCase; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.math.BigDecimal; import java.util.HashMap; +import java.util.Map; public class TestMap extends TestCase { static { -// JsonStream.setMode(EncodingMode.REFLECTION_MODE); +// JsonStream.setMode(EncodingMode.DYNAMIC_MODE); } private ByteArrayOutputStream baos; @@ -37,8 +42,118 @@ public void test_empty() throws IOException { } public void test_null() throws IOException { - stream.writeVal(new TypeLiteral(){}, null); + stream.writeVal(new TypeLiteral() { + }, null); stream.close(); assertEquals("null".replace('\'', '"'), baos.toString()); } + + public void test_value_is_null() throws IOException { + HashMap obj = new HashMap(); + obj.put("hello", null); + stream.writeVal(new TypeLiteral>() { + }, obj); + stream.close(); + assertEquals("{\"hello\":null}", baos.toString()); + } + + public void test_integer_key() throws IOException { + HashMap obj = new HashMap(); + obj.put(100, null); + stream.writeVal(new TypeLiteral>() { + }, obj); + stream.close(); + assertEquals("{\"100\":null}", baos.toString()); + } + + public static enum EnumKey { + KeyA, KeyB + } + + public void test_enum_key() throws IOException { + HashMap obj = new HashMap(); + obj.put(EnumKey.KeyA, null); + stream.writeVal(new TypeLiteral>() { + }, obj); + stream.close(); + assertEquals("{\"KeyA\":null}", baos.toString()); + } + + public static class TestObject1 { + public int Field; + } + + public void test_MapKeyCodec() { + JsoniterSpi.registerMapKeyEncoder(TestObject1.class, new Encoder() { + @Override + public void encode(Object obj, JsonStream stream) throws IOException { + TestObject1 mapKey = (TestObject1) obj; + stream.writeVal(String.valueOf(mapKey.Field)); + } + }); + HashMap obj = new HashMap(); + obj.put(new TestObject1(), null); + String output = JsonStream.serialize(new TypeLiteral>() { + }, obj); + assertEquals("{\"0\":null}", output); + } + + public void skip_indention() { + Map map = new HashMap(); + map.put("field1", "1"); + map.put("field2", "2"); + Config dynamicCfg = new Config.Builder() + .indentionStep(2) + .encodingMode(EncodingMode.DYNAMIC_MODE) + .build(); + String output = JsonStream.serialize(dynamicCfg, map); + assertEquals("{\n" + + " \"field1\": \"1\",\n" + + " \"field2\": \"2\"\n" + + "}", output); + Config reflectionCfg = new Config.Builder() + .indentionStep(2) + .encodingMode(EncodingMode.REFLECTION_MODE) + .build(); + output = JsonStream.serialize(reflectionCfg, map); + assertEquals("{\n" + + " \"field1\": \"1\",\n" + + " \"field2\": \"2\"\n" + + "}", output); + } + + public void test_indention_with_empty_map() { + Config config = JsoniterSpi.getCurrentConfig().copyBuilder() + .indentionStep(2) + .encodingMode(EncodingMode.REFLECTION_MODE) + .build(); + assertEquals("{}", JsonStream.serialize(config, new HashMap())); + config = JsoniterSpi.getCurrentConfig().copyBuilder() + .indentionStep(2) + .encodingMode(EncodingMode.DYNAMIC_MODE) + .build(); + assertEquals("{}", JsonStream.serialize(config, new HashMap())); + } + + public void test_int_as_map_key() { + HashMap m = new HashMap(); + m.put(1, "2"); + assertEquals("{\"1\":\"2\"}", JsonStream.serialize(new TypeLiteral>() { + }, m)); + } + + public void test_object_key() { + HashMap m = new HashMap(); + m.put(1, 2); + assertEquals("{\"1\":2}", JsonStream.serialize(m)); + } + + public void test_multiple_keys() { + HashMap map = new HashMap(); + map.put("destination", "test_destination_value"); + map.put("amount", new BigDecimal("0.0000101101")); + map.put("password", "test_pass"); + final String serialized = JsonStream.serialize(map); + assertEquals(-1, serialized.indexOf("::")); + } } diff --git a/src/test/java/com/jsoniter/output/TestNative.java b/src/test/java/com/jsoniter/output/TestNative.java index 18665f42..c58899ac 100644 --- a/src/test/java/com/jsoniter/output/TestNative.java +++ b/src/test/java/com/jsoniter/output/TestNative.java @@ -28,6 +28,12 @@ public void test_string() throws IOException { assertEquals("'1234567890123456789012345678901234567890'".replace('\'', '"'), baos.toString()); } + public void test_slash() throws IOException { + stream.writeVal("/\\"); + stream.close(); + assertEquals("\"/\\\\\"", baos.toString()); + } + public void test_escape() throws IOException { stream.writeVal("hel\nlo"); stream.close(); @@ -83,6 +89,13 @@ public void test_negative_long() throws IOException { assertEquals("-100", baos.toString()); } + public void test_short() throws IOException { + stream.writeVal(((short)555)); + stream.close(); + assertEquals("555", baos.toString()); + assertEquals("555", JsonStream.serialize(new Short((short)555))); + } + public void test_no_decimal_float() throws IOException { stream.writeVal(100f); stream.close(); @@ -102,15 +115,21 @@ public void test_float3() throws IOException { } public void test_big_float() throws IOException { - stream.writeVal(83886082f); + stream.writeVal((float)0x4ffffff); stream.close(); assertEquals("83886080", baos.toString()); } public void test_double() throws IOException { - stream.writeVal(0.00001d); + stream.writeVal(1.001d); stream.close(); - assertEquals("0.00001", baos.toString()); + assertEquals("1.001", baos.toString()); + } + + public void test_large_double() throws IOException { + stream.writeVal(Double.MAX_VALUE); + stream.close(); + assertEquals("1.7976931348623157E308", baos.toString()); } public void test_boolean() throws IOException { @@ -123,19 +142,20 @@ public void test_boolean() throws IOException { public void test_big_decimal() throws IOException { stream.writeVal(new BigDecimal("12.34")); stream.close(); - assertEquals("'12.34'".replace('\'', '"'), baos.toString()); + assertEquals("12.34".replace('\'', '"'), baos.toString()); } public void test_big_integer() throws IOException { stream.writeVal(new BigInteger("1234")); stream.close(); - assertEquals("'1234'".replace('\'', '"'), baos.toString()); + assertEquals("1234".replace('\'', '"'), baos.toString()); } public void test_raw() throws IOException { stream = new JsonStream(baos, 32); - stream.writeRaw("1234567890123456789012345678901234567890"); + String val = "1234567890123456789012345678901234567890"; + stream.writeRaw(val, val.length()); stream.close(); - assertEquals("1234567890123456789012345678901234567890".replace('\'', '"'), baos.toString()); + assertEquals(val.replace('\'', '"'), baos.toString()); } } diff --git a/src/test/java/com/jsoniter/output/TestNested.java b/src/test/java/com/jsoniter/output/TestNested.java index 4c4263b0..00fd8d6d 100644 --- a/src/test/java/com/jsoniter/output/TestNested.java +++ b/src/test/java/com/jsoniter/output/TestNested.java @@ -1,5 +1,7 @@ package com.jsoniter.output; +import com.jsoniter.annotation.JsonProperty; +import com.jsoniter.spi.JsoniterSpi; import com.jsoniter.spi.TypeLiteral; import junit.framework.TestCase; @@ -12,6 +14,10 @@ public class TestNested extends TestCase { + static { +// JsonStream.setMode(EncodingMode.DYNAMIC_MODE); + } + private ByteArrayOutputStream baos; private JsonStream stream; @@ -29,21 +35,21 @@ public void test_array_of_objects() throws IOException { TestObject1 obj1 = new TestObject1(); obj1.field1 = "1"; obj1.field2 = "2"; - stream.writeVal(new TestObject1[]{obj1}); - stream.close(); - assertEquals("[{'field1':'1','field2':'2'}]".replace('\'', '"'), baos.toString()); + String output = JsonStream.serialize(new TestObject1[]{obj1}); + assertTrue(output.contains("field1")); + assertTrue(output.contains("field2")); } public void test_collection_of_objects() throws IOException { final TestObject1 obj1 = new TestObject1(); obj1.field1 = "1"; obj1.field2 = "2"; - stream.writeVal(new TypeLiteral>() { + String output = JsonStream.serialize(new TypeLiteral>() { }, new ArrayList() {{ add(obj1); }}); - stream.close(); - assertEquals("[{'field1':'1','field2':'2'}]".replace('\'', '"'), baos.toString()); + assertTrue(output.contains("field1")); + assertTrue(output.contains("field2")); } public static class TestObject2 { @@ -51,39 +57,65 @@ public static class TestObject2 { } public void test_object_of_array() throws IOException { - stream.indentionStep = 2; - TestObject2 obj = new TestObject2(); - obj.objs = new TestObject1[1]; - obj.objs[0] = new TestObject1(); - obj.objs[0].field1 = "1"; - obj.objs[0].field2 = "2"; - stream.writeVal(obj); - stream.close(); - assertEquals("{\n" + - " \"objs\":[\n" + - " {\n" + - " \"field1\":\"1\",\n" + - " \"field2\":\"2\"\n" + - " }\n" + - " ]\n" + - "}".replace('\'', '"'), baos.toString()); + if (JsoniterSpi.getCurrentConfig().encodingMode() != EncodingMode.REFLECTION_MODE) { + return; + } + JsonStream.setIndentionStep(2); + try { + TestObject2 obj = new TestObject2(); + obj.objs = new TestObject1[1]; + obj.objs[0] = new TestObject1(); + obj.objs[0].field1 = "1"; + obj.objs[0].field2 = "2"; + stream.writeVal(obj); + stream.close(); + assertEquals("{\n" + + " \"objs\": [\n" + + " {\n" + + " \"field1\": \"1\",\n" + + " \"field2\": \"2\"\n" + + " }\n" + + " ]\n" + + "}".replace('\'', '"'), baos.toString()); + } finally { + JsonStream.setIndentionStep(0); + } } public void test_map_of_objects() throws IOException { - stream.indentionStep = 2; - final TestObject1 obj1 = new TestObject1(); - obj1.field1 = "1"; - obj1.field2 = "2"; - stream.writeVal(new TypeLiteral>() { - }, new HashMap() {{ - put("hello", obj1); - }}); - stream.close(); - assertEquals("{\n" + - " \"hello\":{\n" + - " \"field1\":\"1\",\n" + - " \"field2\":\"2\"\n" + - " }\n" + - "}".replace('\'', '"'), baos.toString()); + if (JsoniterSpi.getCurrentConfig().encodingMode() != EncodingMode.REFLECTION_MODE) { + return; + } + JsonStream.setIndentionStep(2); + try { + final TestObject1 obj1 = new TestObject1(); + obj1.field1 = "1"; + obj1.field2 = "2"; + stream.writeVal(new TypeLiteral>() { + }, new HashMap() {{ + put("hello", obj1); + }}); + stream.close(); + assertEquals("{\n" + + " \"hello\": {\n" + + " \"field1\": \"1\",\n" + + " \"field2\": \"2\"\n" + + " }\n" + + "}".replace('\'', '"'), baos.toString()); + } finally { + JsonStream.setIndentionStep(0); + } + } + + public static class TestObject3 { + @JsonProperty(defaultValueToOmit = "void") + public TestObject3 reference; + } + + public void test_recursive_class() { + // recursive reference will not be supported + // however recursive structure is supported + TestObject3 obj = new TestObject3(); + assertEquals("{\"reference\":null}", JsonStream.serialize(obj)); } } diff --git a/src/test/java/com/jsoniter/output/TestObject.java b/src/test/java/com/jsoniter/output/TestObject.java index 4baf433d..9563b966 100644 --- a/src/test/java/com/jsoniter/output/TestObject.java +++ b/src/test/java/com/jsoniter/output/TestObject.java @@ -1,18 +1,21 @@ package com.jsoniter.output; import com.jsoniter.annotation.JsonIgnore; -import com.jsoniter.annotation.JsoniterAnnotationSupport; +import com.jsoniter.annotation.JsonProperty; +import com.jsoniter.spi.Config; +import com.jsoniter.spi.JsonException; +import com.jsoniter.spi.JsoniterSpi; import com.jsoniter.spi.TypeLiteral; import junit.framework.TestCase; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.*; public class TestObject extends TestCase { static { - JsoniterAnnotationSupport.enable(); - JsonStream.setMode(EncodingMode.DYNAMIC_MODE); +// JsonStream.setMode(EncodingMode.DYNAMIC_MODE); } private ByteArrayOutputStream baos; @@ -44,14 +47,6 @@ public String getField1() { } } - public void test_getter() throws IOException { - TestObject2 obj = new TestObject2(); - obj.field1 = "hello"; - stream.writeVal(obj); - stream.close(); - assertEquals("{'field1':'hello'}".replace('\'', '"'), baos.toString()); - } - public void test_null() throws IOException { stream.writeVal(new TypeLiteral() { }, null); @@ -76,7 +71,7 @@ public void test_null_field() throws IOException { TestObject4 obj = new TestObject4(); stream.writeVal(obj); stream.close(); - assertEquals("{'field1':null}".replace('\'', '"'), baos.toString()); + assertEquals("{\"field1\":null}".replace('\'', '"'), baos.toString()); } public static enum MyEnum { @@ -93,6 +88,13 @@ public void test_enum() throws IOException { stream.writeVal(obj); stream.close(); assertEquals("{'field1':'HELLO'}".replace('\'', '"'), baos.toString()); + Config cfg = new Config.Builder() + .encodingMode(EncodingMode.DYNAMIC_MODE) + .indentionStep(2) + .build(); + assertEquals("{\n" + + " \"field1\": \"HELLO\"\n" + + "}", JsonStream.serialize(cfg, obj)); } public static class TestObject6 { @@ -106,4 +108,318 @@ public void test_array_field() throws IOException { stream.close(); assertEquals("{\"field1\":[1,2,3]}", baos.toString()); } + + public void test_array_field_is_null() throws IOException { + TestObject6 obj = new TestObject6(); + stream.writeVal(obj); + stream.close(); + assertEquals("{\"field1\":null}", baos.toString()); + } + + public static class TestObject7 { + private int[] field1; + + @JsonProperty(defaultValueToOmit = "void") + public int[] getField1() { + return field1; + } + } + + public void test_array_field_is_null_via_getter() throws IOException { + TestObject7 obj = new TestObject7(); + stream.writeVal(obj); + stream.close(); + assertEquals("{\"field1\":null}", baos.toString()); + } + + public static class TestObject8 { + @JsonProperty(nullable = false) + public String[] field1; + } + + public void test_not_nullable() { + TestObject8 obj = new TestObject8(); + obj.field1 = new String[]{"hello"}; + Config config = new Config.Builder() + .encodingMode(EncodingMode.DYNAMIC_MODE) + .build(); + assertEquals("{\"field1\":[\"hello\"]}", + JsonStream.serialize(config, obj)); + try { + JsonStream.serialize(config, new TestObject8()); + fail(); + } catch (NullPointerException ignore) { + } + } + + public static class TestObject9 { + @JsonProperty(collectionValueNullable = false, defaultValueToOmit = "null") + public String[] field1; + @JsonProperty(collectionValueNullable = false, defaultValueToOmit = "null") + public List field2; + @JsonProperty(collectionValueNullable = false, defaultValueToOmit = "null") + public Set field3; + @JsonProperty(collectionValueNullable = false, defaultValueToOmit = "null") + public Map field4; + } + + public void test_collection_value_not_nullable() { + TestObject9 obj = new TestObject9(); + obj.field1 = new String[]{"hello"}; + assertEquals("{\"field1\":[\"hello\"]}", JsonStream.serialize(obj)); + + Config config = new Config.Builder() + .encodingMode(EncodingMode.DYNAMIC_MODE) + .build(); + obj = new TestObject9(); + obj.field1 = new String[]{null}; + try { + JsonStream.serialize(config, obj); + fail(); + } catch (NullPointerException ignore) { + } + + obj = new TestObject9(); + obj.field2 = new ArrayList(); + obj.field2.add(null); + try { + JsonStream.serialize(config, obj); + fail(); + } catch (NullPointerException ignore) { + } + + obj = new TestObject9(); + obj.field3 = new HashSet(); + obj.field3.add(null); + try { + JsonStream.serialize(config, obj); + fail(); + } catch (NullPointerException ignore) { + } + + obj = new TestObject9(); + obj.field4 = new HashMap(); + obj.field4.put("hello", null); + try { + JsonStream.serialize(config, obj); + fail(); + } catch (NullPointerException ignore) { + } + } + + public static class TestObject10 { + @JsonProperty(defaultValueToOmit = "void") + public String field1; + } + + public void test_not_omit_null() { + assertEquals("{\"field1\":null}", JsonStream.serialize(new TestObject10())); + } + + public static class TestObject11 { + @JsonProperty(defaultValueToOmit = "null") + public String field1; + @JsonProperty(defaultValueToOmit = "null") + public String field2; + @JsonProperty(nullable = false) + public Integer field3; + } + + public void test_omit_null() { + assertEquals("{\"field3\":null}", JsonStream.serialize(new TestObject11())); + TestObject11 obj = new TestObject11(); + obj.field1 = "hello"; + assertEquals("{\"field1\":\"hello\",\"field3\":null}", JsonStream.serialize(obj)); + obj = new TestObject11(); + obj.field2 = "hello"; + assertEquals("{\"field2\":\"hello\",\"field3\":null}", JsonStream.serialize(obj)); + obj = new TestObject11(); + obj.field3 = 3; + assertEquals("{\"field3\":3}", JsonStream.serialize(obj)); + } + + + public static class TestObject12 { + public int field1; + + public int getField1() { + return field1; + } + } + + public void test_name_conflict() throws IOException { + TestObject12 obj = new TestObject12(); + stream.writeVal(obj); + stream.close(); + assertEquals("{\"field1\":0}", baos.toString()); + } + + private static class TestObject13 { + } + + public void test_private_class() { + EncodingMode encodingMode = JsoniterSpi.getCurrentConfig().encodingMode(); + if (EncodingMode.REFLECTION_MODE.equals(encodingMode)) { + return; + } + try { + JsonStream.serialize(new TestObject13()); + fail("should throw JsonException"); + } catch (JsonException ignore) { + + } + } + + public static class TestObject14 { + @JsonProperty(nullable = true, defaultValueToOmit = "null") + public String field1; + @JsonProperty(nullable = false) + public String field2; + @JsonProperty(nullable = true, defaultValueToOmit = "void") + public String field3; + } + + public void test_indention() { + Config dynamicCfg = new Config.Builder() + .indentionStep(2) + .encodingMode(EncodingMode.DYNAMIC_MODE) + .build(); + TestObject14 obj = new TestObject14(); + obj.field1 = "1"; + obj.field2 = "2"; + String output = JsonStream.serialize(dynamicCfg, obj); + assertEquals("{\n" + + " \"field1\": \"1\",\n" + + " \"field2\": \"2\",\n" + + " \"field3\": null\n" + + "}", output); + Config reflectionCfg = new Config.Builder() + .indentionStep(2) + .encodingMode(EncodingMode.REFLECTION_MODE) + .build(); + output = JsonStream.serialize(reflectionCfg, obj); + assertEquals("{\n" + + " \"field1\": \"1\",\n" + + " \"field2\": \"2\",\n" + + " \"field3\": null\n" + + "}", output); + } + + public static class TestObject15 { + @JsonProperty(defaultValueToOmit = "null") + public Integer i1; + @JsonProperty(defaultValueToOmit = "null") + public Integer i2; + } + + public void test_indention_with_empty_object() { + Config config = JsoniterSpi.getCurrentConfig().copyBuilder() + .indentionStep(2) + .encodingMode(EncodingMode.REFLECTION_MODE) + .build(); + assertEquals("{}", JsonStream.serialize(config, new TestObject15())); + config = JsoniterSpi.getCurrentConfig().copyBuilder() + .indentionStep(2) + .encodingMode(EncodingMode.DYNAMIC_MODE) + .build(); + assertEquals("{}", JsonStream.serialize(config, new TestObject15())); + } + + public static class TestObject16 { + @JsonProperty(defaultValueToOmit = "void") + public Integer i; + } + + public void test_missing_notFirst() { + Config cfg = JsoniterSpi.getCurrentConfig().copyBuilder() + .indentionStep(2) + .encodingMode(EncodingMode.DYNAMIC_MODE) + .build(); + assertEquals("{\n" + + " \"i\": null\n" + + "}", JsonStream.serialize(cfg, new TestObject16())); + } + + public static class TestObject17 { + public boolean b; + public int i; + public byte bt; + public short s; + public long l = 1; + public float f; + public double d = 1; + public char e; + } + + public void test_omit_default() { + Config cfg = new Config.Builder() + .omitDefaultValue(true) + .build(); + assertEquals("{\"l\":1,\"d\":1}", JsonStream.serialize(cfg, new TestObject17())); + cfg = new Config.Builder() + .omitDefaultValue(true) + .encodingMode(EncodingMode.DYNAMIC_MODE) + .build(); + assertEquals("{\"l\":1,\"d\":1}", JsonStream.serialize(cfg, new TestObject17())); + } + + public static class TestObject18 { + @JsonProperty(defaultValueToOmit = "true") + public boolean b = true; + @JsonProperty(defaultValueToOmit = "true") + public Boolean B = true; + @JsonProperty(defaultValueToOmit = "1") + public int i = 1; + @JsonProperty(defaultValueToOmit = "1") + public Integer I = 1; + @JsonProperty(defaultValueToOmit = "1") + public byte bt = 1; + @JsonProperty(defaultValueToOmit = "1") + public Byte BT = 1; + @JsonProperty(defaultValueToOmit = "1") + public short s = 1; + @JsonProperty(defaultValueToOmit = "1") + public Short S = 1; + @JsonProperty(defaultValueToOmit = "1") + public long l = 1L; + @JsonProperty(defaultValueToOmit = "1") + public Long L = 1L; + @JsonProperty(defaultValueToOmit = "1") + public float f = 1F; + @JsonProperty(defaultValueToOmit = "1") + public Float F = 1F; + @JsonProperty(defaultValueToOmit = "1") + public double d = 1D; + @JsonProperty(defaultValueToOmit = "1") + public Double D = 1D; + @JsonProperty(defaultValueToOmit = "a") + public char c = 'a'; + @JsonProperty(defaultValueToOmit = "a") + public Character C = 'a'; + } + + public void test_omit_selft_defined() { + Config cfg = new Config.Builder() + .omitDefaultValue(true) + .build(); + assertEquals("{}", JsonStream.serialize(cfg, new TestObject18())); + cfg = new Config.Builder() + .omitDefaultValue(true) + .encodingMode(EncodingMode.DYNAMIC_MODE) + .build(); + assertEquals("{}", JsonStream.serialize(cfg, new TestObject18())); + } + + public static class TestObject19 { + public transient int hello; + + public int getHello() { + return hello; + } + } + + public void test_transient_field_getter() { + String output = JsonStream.serialize(new TestObject19()); + assertEquals("{}", output); + } } diff --git a/src/test/java/com/jsoniter/output/TestSpiPropertyEncoder.java b/src/test/java/com/jsoniter/output/TestSpiPropertyEncoder.java new file mode 100644 index 00000000..4fd1ae24 --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestSpiPropertyEncoder.java @@ -0,0 +1,44 @@ +package com.jsoniter.output; + +import com.jsoniter.spi.Encoder; +import com.jsoniter.spi.JsoniterSpi; +import com.jsoniter.spi.TypeLiteral; +import junit.framework.TestCase; +import java.io.IOException; + +public class TestSpiPropertyEncoder extends TestCase { + + public static class TestObject1 { + public String field1; + } + + public void test_PropertyEncoder() throws IOException { + JsoniterSpi.registerPropertyEncoder(TestObject1.class, "field1", new Encoder() { + @Override + public void encode(Object obj, JsonStream stream) throws IOException { + String str = (String) obj; + stream.writeVal(Integer.valueOf(str)); + } + }); + TestObject1 obj = new TestObject1(); + obj.field1 = "100"; + String output = JsonStream.serialize(obj); + assertEquals("{'field1':100}".replace('\'', '"'), output); + } + + public void test_PropertyEncoder_for_type_literal() throws IOException { + TypeLiteral> typeLiteral = new TypeLiteral>() { + }; + JsoniterSpi.registerPropertyEncoder(typeLiteral, "field1", new Encoder() { + @Override + public void encode(Object obj, JsonStream stream) throws IOException { + String str = (String) obj; + stream.writeVal(Integer.valueOf(str) + 1); + } + }); + TestObject1 obj = new TestObject1(); + obj.field1 = "100"; + String output = JsonStream.serialize(typeLiteral, obj); + assertEquals("{'field1':101}".replace('\'', '"'), output); + } +} diff --git a/src/test/java/com/jsoniter/output/TestSpiTypeEncoder.java b/src/test/java/com/jsoniter/output/TestSpiTypeEncoder.java new file mode 100644 index 00000000..c3b7c53b --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestSpiTypeEncoder.java @@ -0,0 +1,53 @@ +package com.jsoniter.output; + +import com.jsoniter.spi.Encoder; +import com.jsoniter.spi.JsoniterSpi; +import com.jsoniter.spi.TypeLiteral; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +public class TestSpiTypeEncoder extends TestCase { + + static { +// JsonStream.setMode(EncodingMode.DYNAMIC_MODE); + } + + public static class MyDate { + Date date; + } + + public void test_TypeEncoder() throws IOException { + JsoniterSpi.registerTypeEncoder(MyDate.class, new Encoder() { + @Override + public void encode(Object obj, JsonStream stream) throws IOException { + MyDate date = (MyDate) obj; + stream.writeVal(date.date.getTime()); + } + }); + System.out.println(JsoniterSpi.getCurrentConfig().configName()); + MyDate myDate = new MyDate(); + myDate.date = new Date(1481365190000L); + String output = JsonStream.serialize(myDate); + assertEquals("1481365190000", output); + } + + public void test_TypeEncoder_for_type_literal() { + TypeLiteral> typeLiteral = new TypeLiteral>() { + }; + JsoniterSpi.registerTypeEncoder(typeLiteral, new Encoder() { + @Override + public void encode(Object obj, JsonStream stream) throws IOException { + List dates = (List) obj; + stream.writeVal(dates.get(0).date.getTime()); + } + }); + MyDate myDate = new MyDate(); + myDate.date = new Date(1481365190000L); + String output = JsonStream.serialize(typeLiteral, Collections.singletonList(myDate)); + assertEquals("1481365190000", output); + } +} diff --git a/src/test/java/com/jsoniter/output/TestStreamBuffer.java b/src/test/java/com/jsoniter/output/TestStreamBuffer.java new file mode 100644 index 00000000..7148cf3c --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestStreamBuffer.java @@ -0,0 +1,68 @@ +package com.jsoniter.output; + +import com.jsoniter.spi.Config; +import com.jsoniter.spi.JsoniterSpi; +import junit.framework.TestCase; + +import java.io.IOException; + +public class TestStreamBuffer extends TestCase { + + public void test_write_string() throws IOException { + JsonStream jsonStream = new JsonStream(null, 32); + jsonStream.writeVal("01234567"); + jsonStream.writeVal("01234567"); + jsonStream.writeVal("012345678"); + jsonStream.writeVal(""); + assertEquals(33, jsonStream.buffer().len()); + } + + public void test_write_raw() throws IOException { + JsonStream jsonStream = new JsonStream(null, 32); + jsonStream.writeRaw("0123456789"); + jsonStream.writeRaw("0123456789"); + jsonStream.writeRaw("0123456789"); + jsonStream.writeRaw("0123456789"); + assertEquals(40, jsonStream.buffer().len()); + } + + public void test_write_bytes() throws IOException { + JsonStream jsonStream = new JsonStream(null, 32); + jsonStream.write("0123456789".getBytes()); + jsonStream.write("0123456789".getBytes()); + jsonStream.write("0123456789".getBytes()); + jsonStream.write("0123456789".getBytes()); + assertEquals(40, jsonStream.buffer().len()); + } + + public void test_write_indention() throws IOException { + Config oldConfig = JsoniterSpi.getCurrentConfig(); + try { + JsoniterSpi.setCurrentConfig(new Config.Builder().indentionStep(32).build()); + JsonStream jsonStream = new JsonStream(null, 32); + jsonStream.writeArrayStart(); + jsonStream.writeIndention(); + assertEquals(34, jsonStream.buffer().len()); + } finally { + JsoniterSpi.setCurrentConfig(oldConfig); + } + } + + public void test_write_int() throws IOException { + JsonStream jsonStream = new JsonStream(null, 32); + jsonStream.writeVal(123456789); + jsonStream.writeVal(123456789); + jsonStream.writeVal(123456789); + jsonStream.writeVal(123456789); + assertEquals(36, jsonStream.buffer().len()); + } + + public void test_write_long() throws IOException { + JsonStream jsonStream = new JsonStream(null, 32); + jsonStream.writeVal(123456789L); + jsonStream.writeVal(123456789L); + jsonStream.writeVal(123456789L); + jsonStream.writeVal(123456789L); + assertEquals(36, jsonStream.buffer().len()); + } +} diff --git a/src/test/java/com/jsoniter/output/TestString.java b/src/test/java/com/jsoniter/output/TestString.java new file mode 100644 index 00000000..186c770a --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestString.java @@ -0,0 +1,40 @@ +package com.jsoniter.output; + +import com.jsoniter.spi.Config; +import com.jsoniter.spi.Config.Builder; +import com.jsoniter.spi.JsoniterSpi; +import java.io.ByteArrayOutputStream; +import junit.framework.TestCase; + +public class TestString extends TestCase { + + public static final String UTF8_GREETING = "Привет čau 你好 ~"; + + public void test_unicode() { + String output = JsonStream.serialize(new Config.Builder().escapeUnicode(false).build(), "中文"); + assertEquals("\"中文\"", output); + } + public void test_unicode_tilde() { + final String tilde = "~"; + String output = JsonStream.serialize(new Config.Builder().escapeUnicode(false).build(), tilde); + assertEquals("\""+tilde+"\"", output); + } + public void test_escape_unicode() { + final Config config = new Builder().escapeUnicode(false).build(); + + assertEquals("\""+UTF8_GREETING+"\"", JsonStream.serialize(config, UTF8_GREETING)); + assertEquals("\""+UTF8_GREETING+"\"", JsonStream.serialize(config.escapeUnicode(), UTF8_GREETING.getClass(), UTF8_GREETING)); + } + public void test_escape_control_character() { + String output = JsonStream.serialize(new String(new byte[]{0})); + assertEquals("\"\\u0000\"", output); + } + public void test_serialize_into_output_stream() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + boolean escapeUnicode = JsoniterSpi.getCurrentConfig().escapeUnicode(); + JsoniterSpi.setCurrentConfig(JsoniterSpi.getCurrentConfig().copyBuilder().escapeUnicode(false).build()); + JsonStream.serialize(String.class, UTF8_GREETING, baos); + JsoniterSpi.setCurrentConfig(JsoniterSpi.getCurrentConfig().copyBuilder().escapeUnicode(escapeUnicode).build()); + assertEquals("\"" + UTF8_GREETING + "\"", baos.toString()); + } +} diff --git a/src/test/java/com/jsoniter/suite/AllTestCases.java b/src/test/java/com/jsoniter/suite/AllTestCases.java new file mode 100644 index 00000000..d3196ed4 --- /dev/null +++ b/src/test/java/com/jsoniter/suite/AllTestCases.java @@ -0,0 +1,63 @@ +package com.jsoniter.suite; + +import com.jsoniter.*; +import com.jsoniter.TestFloat; +import com.jsoniter.TestGenerics; +import com.jsoniter.TestGson; +import com.jsoniter.TestNested; +import com.jsoniter.TestObject; +import com.jsoniter.TestString; +import com.jsoniter.any.TestList; +import com.jsoniter.any.TestLong; +import com.jsoniter.output.*; +import com.jsoniter.output.TestInteger; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + com.jsoniter.TestAnnotationJsonIgnore.class, + com.jsoniter.output.TestAnnotationJsonIgnore.class, + com.jsoniter.TestAnnotationJsonProperty.class, + com.jsoniter.output.TestAnnotationJsonProperty.class, + TestAnnotationJsonWrapper.class, + TestAnnotationJsonUnwrapper.class, + TestAnnotation.class, + TestAnnotationJsonCreator.class, + com.jsoniter.output.TestGenerics.class, + TestCustomizeType.class, TestDemo.class, + TestExisting.class, TestGenerics.class, TestGenerics.class, TestIO.class, + TestNested.class, + com.jsoniter.output.TestNested.class, + TestObject.class, + com.jsoniter.output.TestObject.class, + TestReadAny.class, TestSkip.class, TestSlice.class, + TestString.class, + com.jsoniter.output.TestString.class, + TestWhatIsNext.class, + TestAny.class, + com.jsoniter.output.TestArray.class, + com.jsoniter.any.TestArray.class, + com.jsoniter.TestArray.class, + TestSpiPropertyEncoder.class, + com.jsoniter.TestMap.class, + com.jsoniter.output.TestMap.class, + TestNative.class, + TestBoolean.class, TestFloat.class, com.jsoniter.output.TestFloat.class, + TestList.class, TestInteger.class, com.jsoniter.output.TestInteger.class, + com.jsoniter.output.TestJackson.class, + com.jsoniter.TestJackson.class, + TestSpiTypeEncoder.class, + TestSpiTypeDecoder.class, + TestSpiPropertyDecoder.class, + TestGson.class, + com.jsoniter.output.TestGson.class, + TestStreamBuffer.class, + IterImplForStreamingTest.class, + TestCollection.class, + TestList.class, + TestAnnotationJsonObject.class, + TestLong.class, + TestOmitValue.class}) +public abstract class AllTestCases { +} diff --git a/src/test/java/com/jsoniter/suite/ExtraTests.java b/src/test/java/com/jsoniter/suite/ExtraTests.java new file mode 100644 index 00000000..79b9f70d --- /dev/null +++ b/src/test/java/com/jsoniter/suite/ExtraTests.java @@ -0,0 +1,11 @@ +package com.jsoniter.suite; + +import com.jsoniter.extra.*; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({TestBase64.class, TestNamingStrategy.class, TestPreciseFloat.class}) +public class ExtraTests { + +} diff --git a/src/test/java/com/jsoniter/suite/NonStreamingTests.java b/src/test/java/com/jsoniter/suite/NonStreamingTests.java new file mode 100644 index 00000000..e6ea881d --- /dev/null +++ b/src/test/java/com/jsoniter/suite/NonStreamingTests.java @@ -0,0 +1,13 @@ +package com.jsoniter.suite; + +import com.jsoniter.StreamingCategory; +import org.junit.experimental.categories.Categories; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Categories.class) +@Categories.ExcludeCategory(StreamingCategory.class) +@Suite.SuiteClasses({AllTestCases.class}) +public class NonStreamingTests { + +} diff --git a/src/test/java/com/jsoniter/suite/NonStreamingTests4Hash.java b/src/test/java/com/jsoniter/suite/NonStreamingTests4Hash.java new file mode 100644 index 00000000..6470f2c4 --- /dev/null +++ b/src/test/java/com/jsoniter/suite/NonStreamingTests4Hash.java @@ -0,0 +1,22 @@ +package com.jsoniter.suite; + +import com.jsoniter.spi.DecodingMode; +import com.jsoniter.JsonIterator; +import com.jsoniter.StreamingCategory; +import com.jsoniter.output.EncodingMode; +import com.jsoniter.output.JsonStream; +import org.junit.BeforeClass; +import org.junit.experimental.categories.Categories; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Categories.class) +@Categories.ExcludeCategory(StreamingCategory.class) +@Suite.SuiteClasses({AllTestCases.class}) +public class NonStreamingTests4Hash { + @BeforeClass + public static void setup() { + JsonStream.setMode(EncodingMode.DYNAMIC_MODE); + JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH); + } +} diff --git a/src/test/java/com/jsoniter/suite/NonStreamingTests4Strict.java b/src/test/java/com/jsoniter/suite/NonStreamingTests4Strict.java new file mode 100644 index 00000000..8bfea647 --- /dev/null +++ b/src/test/java/com/jsoniter/suite/NonStreamingTests4Strict.java @@ -0,0 +1,23 @@ +package com.jsoniter.suite; + +import com.jsoniter.spi.DecodingMode; +import com.jsoniter.JsonIterator; +import com.jsoniter.StreamingCategory; +import com.jsoniter.output.EncodingMode; +import com.jsoniter.output.JsonStream; +import org.junit.BeforeClass; +import org.junit.experimental.categories.Categories; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + + +@RunWith(Categories.class) +@Categories.ExcludeCategory(StreamingCategory.class) +@Suite.SuiteClasses({AllTestCases.class}) +public class NonStreamingTests4Strict { + @BeforeClass + public static void setup() { + JsonStream.setMode(EncodingMode.DYNAMIC_MODE); + JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY); + } +} diff --git a/src/test/java/com/jsoniter/suite/StreamingTests.java b/src/test/java/com/jsoniter/suite/StreamingTests.java new file mode 100644 index 00000000..237196fb --- /dev/null +++ b/src/test/java/com/jsoniter/suite/StreamingTests.java @@ -0,0 +1,17 @@ +package com.jsoniter.suite; + +import com.jsoniter.JsonIterator; +import org.junit.BeforeClass; +import org.junit.experimental.categories.Categories; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Categories.class) +@Suite.SuiteClasses({AllTestCases.class}) +public class StreamingTests { + + @BeforeClass + public static void setup() { + JsonIterator.enableStreamingSupport(); + } +} diff --git a/src/test/tweets.json b/src/test/tweets.json new file mode 100644 index 00000000..9e07d4ac --- /dev/null +++ b/src/test/tweets.json @@ -0,0 +1,1802 @@ +[ + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 22:33:07 +0000 2011", + "truncated": true, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 22:31:38 +0000 2011", + "truncated": false, + "id_str": "60833028892667904", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Watch the @NHL's newest History Will Be Made video featuring last night's incredible comeback by the #Sharks http:\/\/bit.ly\/i86L60 #SJSLAK", + "id": 60833028892667904, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "San Jose Sharks", + "profile_sidebar_border_color": "f3901d", + "profile_background_tile": false, + "profile_sidebar_fill_color": "000000", + "created_at": "Tue Mar 31 20:57:57 +0000 2009", + "location": "San Jose, CA", + "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/1014299634\/twitter9_normal.jpg", + "follow_request_sent": false, + "profile_link_color": "f3901d", + "is_translator": false, + "id_str": "27961547", + "favourites_count": 23, + "contributors_enabled": true, + "url": "http:\/\/SJSHARKS.com", + "default_profile": false, + "utc_offset": -28800, + "id": 27961547, + "profile_use_background_image": true, + "listed_count": 1732, + "lang": "en", + "protected": false, + "followers_count": 29603, + "profile_text_color": "00788b", + "profile_background_color": "00788b", + "time_zone": "Pacific Time (US & Canada)", + "description": "The Official Twitter Page of the San Jose Sharks.", + "notifications": false, + "geo_enabled": false, + "verified": true, + "profile_background_image_url": "http:\/\/a3.twimg.com\/profile_background_images\/32524835\/twitter7.jpg", + "default_profile_image": false, + "friends_count": 78, + "statuses_count": 1699, + "screen_name": "SanJoseSharks", + "following": false, + "show_all_inline_media": false + }, + "source": "\u003Ca href=\"http:\/\/www.tweetdeck.com\" rel=\"nofollow\"\u003ETweetDeck\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + "id_str": "60833399132258306", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @SanJoseSharks: Watch the @NHL's newest History Will Be Made video featuring last night's incredible comeback by the #Sharks http:\/\/b ...", + "id": 60833399132258306, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "brooke", + "profile_sidebar_border_color": "000000", + "profile_background_tile": false, + "profile_sidebar_fill_color": "F5C7C9", + "created_at": "Mon Mar 24 17:16:26 +0000 2008", + "location": "california", + "profile_image_url": "http:\/\/a1.twimg.com\/profile_images\/58567276\/2755186893_c7b31b651b_b_normal.jpg", + "follow_request_sent": false, + "profile_link_color": "777777", + "is_translator": false, + "id_str": "14208894", + "favourites_count": 4, + "contributors_enabled": false, + "url": null, + "default_profile": false, + "utc_offset": -28800, + "id": 14208894, + "profile_use_background_image": true, + "listed_count": 3, + "lang": "en", + "protected": false, + "followers_count": 97, + "profile_text_color": "000000", + "profile_background_color": "000000", + "time_zone": "Pacific Time (US & Canada)", + "description": "i'm neat", + "notifications": false, + "geo_enabled": false, + "verified": false, + "profile_background_image_url": "http:\/\/a0.twimg.com\/profile_background_images\/66730001\/ahflowerscribble.br.jpg", + "default_profile_image": false, + "friends_count": 96, + "statuses_count": 3319, + "screen_name": "b2therooke", + "following": true, + "show_all_inline_media": false + }, + "source": "\u003Ca href=\"http:\/\/mobile.twitter.com\" rel=\"nofollow\"\u003ETwitter for Android\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 22:25:45 +0000 2011", + "truncated": true, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:15:13 +0000 2011", + "truncated": false, + "id_str": "60813797417422848", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Just added support for file sharing regions upload of the same file in AHC. Mini bittorent supports like :-). In #jcloud soon. #ahc 1.6.4", + "id": 60813797417422848, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "jfarcand", + "profile_sidebar_border_color": "BDDCAD", + "profile_background_tile": false, + "profile_sidebar_fill_color": "DDFFCC", + "created_at": "Mon Dec 22 02:46:16 +0000 2008", + "location": "Pr\u00e9vost", + "profile_image_url": "http:\/\/a2.twimg.com\/profile_images\/1292846058\/pouet_normal.png", + "profile_link_color": "0084B4", + "is_translator": false, + "id_str": "18298703", + "follow_request_sent": false, + "contributors_enabled": false, + "favourites_count": 0, + "url": "http:\/\/jfarcand.wordpress.com\/", + "default_profile": false, + "utc_offset": -21600, + "id": 18298703, + "profile_use_background_image": true, + "listed_count": 56, + "lang": "en", + "protected": false, + "followers_count": 681, + "profile_text_color": "333333", + "profile_background_color": "9AE4E8", + "time_zone": "Central Time (US & Canada)", + "geo_enabled": false, + "description": "Objecteur de croissance, Open Source worker, etc ... I am the creator of @atmo_framework", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a2.twimg.com\/profile_background_images\/56304182\/IMG_0157.jpg", + "default_profile_image": false, + "statuses_count": 2018, + "friends_count": 98, + "screen_name": "jfarcand", + "show_all_inline_media": false, + "following": false + }, + "source": "\u003Ca href=\"http:\/\/twitter.com\" rel=\"nofollow\"\u003ETweetie for Mac\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + "id_str": "60831548257214464", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @jfarcand: Just added support for file sharing regions upload of the same file in AHC. Mini bittorent supports like :-). In #jcloud s ...", + "id": 60831548257214464, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Stuart McCulloch", + "profile_sidebar_border_color": "C0DEED", + "profile_background_tile": false, + "profile_sidebar_fill_color": "DDEEF6", + "created_at": "Sun Sep 07 05:22:51 +0000 2008", + "location": "Kenilworth", + "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/59578326\/hmm_normal.png", + "profile_link_color": "0084B4", + "is_translator": false, + "id_str": "16166414", + "follow_request_sent": false, + "contributors_enabled": false, + "favourites_count": 0, + "url": "http:\/\/mcculls.blogspot.com", + "default_profile": true, + "utc_offset": 0, + "id": 16166414, + "profile_use_background_image": true, + "listed_count": 16, + "lang": "en", + "protected": false, + "followers_count": 227, + "profile_text_color": "333333", + "profile_background_color": "C0DEED", + "time_zone": "London", + "geo_enabled": false, + "description": "Three computers, two cats, one adorable wife", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a3.twimg.com\/a\/1303316982\/images\/themes\/theme1\/bg.png", + "default_profile_image": false, + "statuses_count": 1923, + "friends_count": 76, + "screen_name": "mcculls", + "show_all_inline_media": false, + "following": true + }, + "source": "\u003Ca href=\"http:\/\/twitter.com\/\" rel=\"nofollow\"\u003ETwitter for iPhone\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 22:18:12 +0000 2011", + "truncated": false, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 14:20:13 +0000 2011", + "truncated": false, + "id_str": "60709359147171840", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Girls believe in what they hear and boys in what they see, that's why girls wear make up and boys lie...", + "id": 60709359147171840, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Marina Orlova", + "profile_sidebar_border_color": "fff8ad", + "profile_background_tile": true, + "profile_sidebar_fill_color": "f6ffd1", + "created_at": "Wed Jun 25 04:44:45 +0000 2008", + "location": "Los Angeles", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/1286057133\/Marina0024MasterTIF_normal.jpg", + "follow_request_sent": false, + "profile_link_color": "0099CC", + "is_translator": false, + "id_str": "15227650", + "favourites_count": 2, + "contributors_enabled": false, + "url": "http:\/\/www.hotforwords.com", + "default_profile": false, + "utc_offset": -28800, + "id": 15227650, + "profile_use_background_image": true, + "listed_count": 1639, + "lang": "en", + "protected": false, + "followers_count": 38649, + "profile_text_color": "333333", + "profile_background_color": "FFF04D", + "time_zone": "Pacific Time (US & Canada)", + "description": "Philologist. Putting the LOL in PhiLOLogy :-)", + "notifications": false, + "geo_enabled": false, + "verified": false, + "profile_background_image_url": "http:\/\/a3.twimg.com\/profile_background_images\/199908976\/_MG_8441-1.JPG", + "default_profile_image": false, + "friends_count": 164, + "statuses_count": 7112, + "screen_name": "hotforwords", + "following": false, + "show_all_inline_media": true + }, + "source": "\u003Ca href=\"http:\/\/twitter.com\/\" rel=\"nofollow\"\u003ETwitter for iPhone\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + "id_str": "60829646584938496", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @hotforwords: Girls believe in what they hear and boys in what they see, that's why girls wear make up and boys lie...", + "id": 60829646584938496, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Graham Cale", + "profile_sidebar_border_color": "94a4ae", + "profile_background_tile": false, + "profile_sidebar_fill_color": "1d110a", + "created_at": "Thu Nov 25 21:29:04 +0000 2010", + "location": "Kitchener, ON", + "profile_image_url": "http:\/\/a1.twimg.com\/profile_images\/1181276241\/image_normal.jpg", + "follow_request_sent": false, + "profile_link_color": "94a4ae", + "is_translator": false, + "id_str": "219781561", + "favourites_count": 0, + "contributors_enabled": false, + "url": "http:\/\/www.google.com\/reader\/shared\/graham.cale", + "default_profile": false, + "utc_offset": -21600, + "id": 219781561, + "profile_use_background_image": true, + "listed_count": 0, + "lang": "en", + "protected": false, + "followers_count": 14, + "profile_text_color": "a98e6f", + "profile_background_color": "251810", + "time_zone": "Central Time (US & Canada)", + "description": "news junkie.", + "notifications": false, + "geo_enabled": false, + "verified": false, + "profile_background_image_url": "http:\/\/a0.twimg.com\/profile_background_images\/178310231\/x50baa944daf25caf24f2a2ff5a19f59.jpg", + "default_profile_image": false, + "friends_count": 49, + "statuses_count": 128, + "screen_name": "grahamcale", + "following": true, + "show_all_inline_media": false + }, + "source": "\u003Ca href=\"http:\/\/mobile.twitter.com\" rel=\"nofollow\"\u003ETwitter for Android\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 22:17:15 +0000 2011", + "truncated": true, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 19:49:18 +0000 2011", + "truncated": false, + "id_str": "60792172630380544", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "We just played wedraw.tv, great fun, iPhone, iPad, Androids & GoogleTV all playing together. TV social gaming win. Great job @thinkmovl", + "id": 60792172630380544, + "retweet_count": 1, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Richard Leggett", + "profile_sidebar_border_color": "eeeeee", + "profile_background_tile": true, + "profile_sidebar_fill_color": "efefef", + "created_at": "Fri Feb 13 09:30:10 +0000 2009", + "location": "Milton Keynes", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/1167979493\/concretecowboy_icon_normal.jpg", + "profile_link_color": "009999", + "is_translator": false, + "follow_request_sent": false, + "id_str": "20758989", + "favourites_count": 11, + "url": "http:\/\/www.richardleggett.co.uk", + "contributors_enabled": false, + "default_profile": false, + "utc_offset": 0, + "id": 20758989, + "profile_use_background_image": true, + "listed_count": 86, + "lang": "en", + "protected": false, + "followers_count": 879, + "profile_text_color": "333333", + "profile_background_color": "131516", + "time_zone": "London", + "geo_enabled": false, + "description": "Founder of Valis Interactive. Develops Mobile Android\/Win Phone 7\/iOS, Flash, Flex, AIR.", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a2.twimg.com\/a\/1302888170\/images\/themes\/theme14\/bg.gif", + "default_profile_image": false, + "statuses_count": 7427, + "friends_count": 424, + "screen_name": "richardleggett", + "show_all_inline_media": true, + "following": false + }, + "source": "\u003Ca href=\"http:\/\/www.tweetdeck.com\" rel=\"nofollow\"\u003ETweetDeck\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + "id_str": "60829408910520320", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @richardleggett: We just played wedraw.tv, great fun, iPhone, iPad, Androids & GoogleTV all playing together. TV social gaming win. G ...", + "id": 60829408910520320, + "retweet_count": 1, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Charlie Collins", + "profile_sidebar_border_color": "BDDCAD", + "profile_background_tile": false, + "profile_sidebar_fill_color": "DDFFCC", + "created_at": "Mon Jan 12 16:01:37 +0000 2009", + "location": "Atlanta, GA, US", + "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/599143959\/ch5_normal.jpg", + "profile_link_color": "0084B4", + "is_translator": false, + "follow_request_sent": false, + "id_str": "18904477", + "favourites_count": 7, + "url": "http:\/\/www.google.com\/profiles\/charlie.collins", + "contributors_enabled": false, + "default_profile": false, + "utc_offset": -18000, + "id": 18904477, + "profile_use_background_image": true, + "listed_count": 26, + "lang": "en", + "protected": false, + "followers_count": 250, + "profile_text_color": "333333", + "profile_background_color": "9AE4E8", + "time_zone": "Eastern Time (US & Canada)", + "geo_enabled": false, + "description": "Father, husband, code grunt, author.", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a1.twimg.com\/profile_background_images\/5453734\/orange_wall_cut.jpg", + "default_profile_image": false, + "statuses_count": 2748, + "friends_count": 167, + "screen_name": "CharlieCollins", + "show_all_inline_media": false, + "following": false + }, + "source": "\u003Ca href=\"http:\/\/seesmic.com\/app\" rel=\"nofollow\"\u003ESeesmic Web\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 22:13:24 +0000 2011", + "truncated": false, + "id_str": "60828439833350144", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Saskboy Rips Down This Wall: http:\/\/t.co\/cPeDaB9 - Brad Wall unfairly attacks @M_Ignatieff, so I fairly rip Wall. #skpoli #cdnpoli #elxn41", + "id": 60828439833350144, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Saskboy K.", + "profile_sidebar_border_color": "181A1E", + "profile_background_tile": false, + "profile_sidebar_fill_color": "252429", + "created_at": "Thu Jun 07 04:43:39 +0000 2007", + "location": "Saskatchewan, Canada", + "profile_image_url": "http:\/\/a1.twimg.com\/profile_images\/1174293129\/Img_6178riders_normal.jpg", + "is_translator": false, + "profile_link_color": "2FC2EF", + "follow_request_sent": false, + "id_str": "6634632", + "favourites_count": 957, + "default_profile": false, + "url": "http:\/\/www.abandonedstuff.com", + "contributors_enabled": false, + "utc_offset": -21600, + "id": 6634632, + "profile_use_background_image": true, + "listed_count": 58, + "lang": "en", + "protected": false, + "followers_count": 858, + "profile_text_color": "666666", + "profile_background_color": "1A1B1F", + "time_zone": "Saskatchewan", + "description": "A Saskatchewan Blogger living in Regina", + "notifications": false, + "geo_enabled": false, + "verified": false, + "profile_background_image_url": "http:\/\/a2.twimg.com\/profile_background_images\/10281422\/twitrgrid.JPG", + "friends_count": 580, + "statuses_count": 5715, + "default_profile_image": false, + "screen_name": "saskboy", + "following": true, + "show_all_inline_media": false + }, + "source": "\u003Ca href=\"http:\/\/twitter.com\/tweetbutton\" rel=\"nofollow\"\u003ETweet Button\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 22:02:52 +0000 2011", + "truncated": true, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:55:37 +0000 2011", + "truncated": false, + "id_str": "60823963495956480", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "$3.99 on @AmazonMP3! @BombaEstereo @DJAfro Los @AmgsInvisibles @Monareta @Nortec_Fussible @ThePinkerTones http:\/\/amzn.to\/e6GWZG", + "id": 60823963495956480, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Nacional Records", + "profile_sidebar_border_color": "bfbfbf", + "profile_background_tile": true, + "profile_sidebar_fill_color": "c9c9c9", + "created_at": "Wed Apr 15 23:29:11 +0000 2009", + "location": "North Hollywood, CA", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/1081992285\/iTunes-essentials3_normal.jpg", + "is_translator": false, + "follow_request_sent": false, + "profile_link_color": "c34242", + "id_str": "31560203", + "favourites_count": 1, + "contributors_enabled": true, + "default_profile": false, + "url": "http:\/\/www.nacionalrecords.com\/", + "utc_offset": -28800, + "id": 31560203, + "profile_use_background_image": true, + "listed_count": 329, + "lang": "en", + "protected": false, + "followers_count": 8899, + "profile_text_color": "1c1f23", + "profile_background_color": "07090b", + "time_zone": "Pacific Time (US & Canada)", + "geo_enabled": false, + "description": "Manu Chao, Fabulosos Cadillacs, Nortec Collective, Aterciopelados, Amigos Invisibles, Mexican Institute of Sound, Bomba Estereo, Ana Tijoux, Pacha Massive y mas", + "notifications": false, + "verified": true, + "profile_background_image_url": "http:\/\/a1.twimg.com\/profile_background_images\/124317630\/x044fab92d37ca58eac62f5d9a8db1a1.png", + "statuses_count": 7366, + "default_profile_image": false, + "friends_count": 2579, + "screen_name": "NacionalRecords", + "following": false, + "show_all_inline_media": false + }, + "source": "web", + "in_reply_to_status_id": null + }, + "id_str": "60825788555079681", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @NacionalRecords: $3.99 on @AmazonMP3! @BombaEstereo @DJAfro Los @AmgsInvisibles @Monareta @Nortec_Fussible @ThePinkerTones http:\/\/am ...", + "id": 60825788555079681, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Amazon MP3", + "profile_sidebar_border_color": "99cc33", + "profile_background_tile": false, + "profile_sidebar_fill_color": "ebebeb", + "created_at": "Mon May 12 04:02:08 +0000 2008", + "location": "Seattle", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/340137940\/twitteravatar_normal.jpeg", + "is_translator": false, + "follow_request_sent": false, + "profile_link_color": "669933", + "id_str": "14740219", + "favourites_count": 28, + "contributors_enabled": true, + "default_profile": false, + "url": "http:\/\/www.amazonmp3.com", + "utc_offset": -28800, + "id": 14740219, + "profile_use_background_image": true, + "listed_count": 8251, + "lang": "en", + "protected": false, + "followers_count": 1539675, + "profile_text_color": "000000", + "profile_background_color": "000000", + "time_zone": "Pacific Time (US & Canada)", + "geo_enabled": false, + "description": "Daily Deals and special sales on DRM-free, play-anywhere music downloads from Amazon. The official Amazon MP3 twitter feed.", + "notifications": false, + "verified": true, + "profile_background_image_url": "http:\/\/a3.twimg.com\/profile_background_images\/53396456\/bg_w_url.gif", + "statuses_count": 3433, + "default_profile_image": false, + "friends_count": 698, + "screen_name": "amazonmp3", + "following": true, + "show_all_inline_media": false + }, + "source": "\u003Ca href=\"http:\/\/www.tweetdeck.com\" rel=\"nofollow\"\u003ETweetDeck\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 22:02:02 +0000 2011", + "truncated": false, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:18:23 +0000 2011", + "truncated": false, + "id_str": "60814594846887936", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Only 5 days left to apply for our Start Up, Boot Up competition! Nice write up today on it - http:\/\/t.co\/tHFTXZg #contegix", + "id": 60814594846887936, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Matthew E. Porter", + "profile_sidebar_border_color": "C0DEED", + "profile_background_tile": false, + "profile_sidebar_fill_color": "DDEEF6", + "created_at": "Fri Nov 30 16:15:23 +0000 2007", + "location": "Saint Louis, MO", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/1242839739\/161641_664286491_2805878_n_normal.jpg", + "profile_link_color": "0084B4", + "is_translator": false, + "follow_request_sent": false, + "id_str": "10742502", + "contributors_enabled": false, + "favourites_count": 14, + "url": "http:\/\/www.porterhome.com\/blog\/matthew", + "default_profile": true, + "utc_offset": -21600, + "id": 10742502, + "profile_use_background_image": true, + "listed_count": 45, + "lang": "en", + "protected": false, + "followers_count": 694, + "profile_text_color": "333333", + "profile_background_color": "C0DEED", + "geo_enabled": false, + "time_zone": "Central Time (US & Canada)", + "description": "Father, geek, entrepreneur - in that order.", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a3.twimg.com\/a\/1302646548\/images\/themes\/theme1\/bg.png", + "default_profile_image": false, + "friends_count": 592, + "statuses_count": 4804, + "show_all_inline_media": false, + "screen_name": "meporter", + "following": true + }, + "source": "\u003Ca href=\"http:\/\/itunes.apple.com\/us\/app\/twitter\/id409789998?mt=12\" rel=\"nofollow\"\u003ETwitter for Mac\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + "id_str": "60825579515162624", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @meporter: Only 5 days left to apply for our Start Up, Boot Up competition! Nice write up today on it - http:\/\/t.co\/tHFTXZg #contegix", + "id": 60825579515162624, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Alex Miller", + "profile_sidebar_border_color": "000000", + "profile_background_tile": true, + "profile_sidebar_fill_color": "b9e250", + "created_at": "Mon Apr 28 14:04:13 +0000 2008", + "location": "St. Louis", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/547362531\/nachos_normal.jpg", + "profile_link_color": "43556b", + "is_translator": false, + "follow_request_sent": false, + "id_str": "14569541", + "contributors_enabled": false, + "favourites_count": 13, + "url": "http:\/\/tech.puredanger.com", + "default_profile": false, + "utc_offset": -21600, + "id": 14569541, + "profile_use_background_image": true, + "listed_count": 378, + "lang": "en", + "protected": false, + "followers_count": 3977, + "profile_text_color": "000000", + "profile_background_color": "000000", + "geo_enabled": false, + "time_zone": "Central Time (US & Canada)", + "description": "Java, Clojure, JVM, concurrency, sem web, Strange Loop, Lambda Lounge, Revelytix, nachos, beer, music", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a1.twimg.com\/profile_background_images\/56421963\/DSC_0081.JPG", + "default_profile_image": false, + "friends_count": 4286, + "statuses_count": 7697, + "show_all_inline_media": true, + "screen_name": "puredanger", + "following": false + }, + "source": "\u003Ca href=\"http:\/\/www.nambu.com\/\" rel=\"nofollow\"\u003ENambu\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 22:01:45 +0000 2011", + "truncated": false, + "id_str": "60825505389219840", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @strangeloop_stl Have I mentioned that Steve Yegge is coming to #strangeloop this year? 'cause he is.", + "id": 60825505389219840, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Alex Miller", + "profile_sidebar_border_color": "000000", + "profile_background_tile": true, + "profile_sidebar_fill_color": "b9e250", + "created_at": "Mon Apr 28 14:04:13 +0000 2008", + "location": "St. Louis", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/547362531\/nachos_normal.jpg", + "is_translator": false, + "profile_link_color": "43556b", + "follow_request_sent": false, + "id_str": "14569541", + "favourites_count": 13, + "default_profile": false, + "url": "http:\/\/tech.puredanger.com", + "contributors_enabled": false, + "utc_offset": -21600, + "id": 14569541, + "profile_use_background_image": true, + "listed_count": 378, + "lang": "en", + "protected": false, + "followers_count": 3977, + "profile_text_color": "000000", + "profile_background_color": "000000", + "time_zone": "Central Time (US & Canada)", + "description": "Java, Clojure, JVM, concurrency, sem web, Strange Loop, Lambda Lounge, Revelytix, nachos, beer, music", + "notifications": false, + "geo_enabled": false, + "verified": false, + "profile_background_image_url": "http:\/\/a1.twimg.com\/profile_background_images\/56421963\/DSC_0081.JPG", + "friends_count": 4286, + "statuses_count": 7697, + "default_profile_image": false, + "screen_name": "puredanger", + "following": true, + "show_all_inline_media": true + }, + "source": "\u003Ca href=\"http:\/\/www.nambu.com\/\" rel=\"nofollow\"\u003ENambu\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:48:21 +0000 2011", + "truncated": false, + "id_str": "60822136159350784", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "I and @stuartsierra will be teaching @pragstudio #clojure June 22-24 http:\/\/bit.ly\/dgMFHJ", + "id": 60822136159350784, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "stuarthalloway", + "profile_sidebar_border_color": "C0DEED", + "profile_background_tile": false, + "profile_sidebar_fill_color": "DDEEF6", + "created_at": "Thu Mar 20 14:38:29 +0000 2008", + "location": "Chapel Hill, NC, USA", + "profile_image_url": "http:\/\/a2.twimg.com\/profile_images\/51921564\/stu-small_normal.png", + "is_translator": false, + "follow_request_sent": false, + "profile_link_color": "0084B4", + "id_str": "14184390", + "favourites_count": 0, + "contributors_enabled": false, + "default_profile": true, + "url": "http:\/\/thinkrelevance.com", + "utc_offset": -18000, + "id": 14184390, + "listed_count": 308, + "profile_use_background_image": true, + "lang": "en", + "protected": false, + "followers_count": 2710, + "profile_text_color": "333333", + "profile_background_color": "C0DEED", + "geo_enabled": false, + "time_zone": "Quito", + "description": "husband\/father\/coder\/runner", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a3.twimg.com\/a\/1302724321\/images\/themes\/theme1\/bg.png", + "statuses_count": 628, + "friends_count": 276, + "default_profile_image": false, + "show_all_inline_media": false, + "screen_name": "stuarthalloway", + "following": true + }, + "source": "\u003Ca href=\"http:\/\/www.socialoomph.com\" rel=\"nofollow\"\u003ESocialOomph\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:43:51 +0000 2011", + "truncated": true, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:38:11 +0000 2011", + "truncated": false, + "id_str": "60819576446926848", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "I say on July 1st we all organize a #democracymob across the country so they all know we won't go away after May #elxn41 are you with me?", + "id": 60819576446926848, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Mark McCaw", + "profile_sidebar_border_color": "adf1fc", + "profile_background_tile": false, + "profile_sidebar_fill_color": "000000", + "created_at": "Mon Jan 17 13:50:36 +0000 2011", + "location": "Moncton, NB Canada", + "profile_image_url": "http:\/\/a1.twimg.com\/profile_images\/1219569613\/Mark_At_Niagara_normal.jpg", + "is_translator": false, + "follow_request_sent": false, + "profile_link_color": "fa8459", + "id_str": "239378797", + "favourites_count": 1, + "default_profile": false, + "contributors_enabled": false, + "url": null, + "utc_offset": -14400, + "id": 239378797, + "profile_use_background_image": true, + "listed_count": 8, + "lang": "en", + "protected": false, + "followers_count": 197, + "profile_text_color": "947974", + "profile_background_color": "030103", + "time_zone": "Atlantic Time (Canada)", + "geo_enabled": true, + "description": "Learning junkie, political junkie, hockey junkie but not a junkie junkie.", + "notifications": false, + "verified": false, + "friends_count": 64, + "profile_background_image_url": "http:\/\/a0.twimg.com\/profile_background_images\/213468121\/x975ab12b9c65eb76ce02ca3194b75c7.jpg", + "statuses_count": 3349, + "default_profile_image": false, + "screen_name": "bigpicguy", + "following": false, + "show_all_inline_media": false + }, + "source": "web", + "in_reply_to_status_id": null + }, + "id_str": "60821003848261633", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @bigpicguy: I say on July 1st we all organize a #democracymob across the country so they all know we won't go away after May #elxn41 ...", + "id": 60821003848261633, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Saskboy K.", + "profile_sidebar_border_color": "181A1E", + "profile_background_tile": false, + "profile_sidebar_fill_color": "252429", + "created_at": "Thu Jun 07 04:43:39 +0000 2007", + "location": "Saskatchewan, Canada", + "profile_image_url": "http:\/\/a1.twimg.com\/profile_images\/1174293129\/Img_6178riders_normal.jpg", + "is_translator": false, + "follow_request_sent": false, + "profile_link_color": "2FC2EF", + "id_str": "6634632", + "favourites_count": 957, + "default_profile": false, + "contributors_enabled": false, + "url": "http:\/\/www.abandonedstuff.com", + "utc_offset": -21600, + "id": 6634632, + "profile_use_background_image": true, + "listed_count": 57, + "lang": "en", + "protected": false, + "followers_count": 856, + "profile_text_color": "666666", + "profile_background_color": "1A1B1F", + "time_zone": "Saskatchewan", + "geo_enabled": false, + "description": "A Saskatchewan Blogger living in Regina", + "notifications": false, + "verified": false, + "friends_count": 580, + "profile_background_image_url": "http:\/\/a2.twimg.com\/profile_background_images\/10281422\/twitrgrid.JPG", + "statuses_count": 5709, + "default_profile_image": false, + "screen_name": "saskboy", + "following": true, + "show_all_inline_media": false + }, + "source": "web", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:41:50 +0000 2011", + "truncated": true, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:39:36 +0000 2011", + "truncated": false, + "id_str": "60819933671596032", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Scary to see how little he knows! MT @MacleansMag SK Premier Brad Wall decides that the nation needs his constitut... http:\/\/bit.ly\/gTUmrS", + "id": 60819933671596032, + "retweet_count": 2, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Tom Flemming", + "profile_sidebar_border_color": "659430", + "profile_background_tile": true, + "profile_sidebar_fill_color": "a5b0b3", + "created_at": "Tue Mar 17 14:49:36 +0000 2009", + "location": "Hamilton, ON", + "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/1288479752\/tomflickr_normal.jpg", + "is_translator": false, + "follow_request_sent": false, + "profile_link_color": "9c8109", + "id_str": "24892031", + "favourites_count": 750, + "contributors_enabled": false, + "url": "http:\/\/www.diigo.com\/user\/tomflem", + "default_profile": false, + "utc_offset": -18000, + "id": 24892031, + "listed_count": 79, + "profile_use_background_image": true, + "lang": "en", + "protected": false, + "followers_count": 894, + "profile_text_color": "574410", + "profile_background_color": "a2dbab", + "time_zone": "Eastern Time (US & Canada)", + "geo_enabled": false, + "description": "Formerly a health sciences librarian at McMaster University (ON) and at Dalhousie University (NS) in Canada", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a1.twimg.com\/profile_background_images\/47634442\/stones.jpg", + "friends_count": 389, + "statuses_count": 21099, + "default_profile_image": false, + "screen_name": "tomflem", + "show_all_inline_media": false, + "following": true + }, + "source": "web", + "in_reply_to_status_id": null + }, + "id_str": "60820493967691776", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @tomflem: Scary to see how little he knows! MT @MacleansMag SK Premier Brad Wall decides that the nation needs his constitut... http ...", + "id": 60820493967691776, + "retweet_count": 2, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Saskboy K.", + "profile_sidebar_border_color": "181A1E", + "profile_background_tile": false, + "profile_sidebar_fill_color": "252429", + "created_at": "Thu Jun 07 04:43:39 +0000 2007", + "location": "Saskatchewan, Canada", + "profile_image_url": "http:\/\/a1.twimg.com\/profile_images\/1174293129\/Img_6178riders_normal.jpg", + "is_translator": false, + "follow_request_sent": false, + "profile_link_color": "2FC2EF", + "id_str": "6634632", + "favourites_count": 955, + "contributors_enabled": false, + "url": "http:\/\/www.abandonedstuff.com", + "default_profile": false, + "utc_offset": -21600, + "id": 6634632, + "listed_count": 57, + "profile_use_background_image": true, + "lang": "en", + "protected": false, + "followers_count": 857, + "profile_text_color": "666666", + "profile_background_color": "1A1B1F", + "time_zone": "Saskatchewan", + "geo_enabled": false, + "description": "A Saskatchewan Blogger living in Regina", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a2.twimg.com\/profile_background_images\/10281422\/twitrgrid.JPG", + "friends_count": 580, + "statuses_count": 5707, + "default_profile_image": false, + "screen_name": "saskboy", + "show_all_inline_media": false, + "following": false + }, + "source": "web", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:41:30 +0000 2011", + "truncated": true, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:39:56 +0000 2011", + "truncated": false, + "id_str": "60820015481499648", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @bigpicguy: @CBC just said the government was brought down bec opposition didn't like budget? HELLO CONTEMPT #elxn41 #youthvote...", + "id": 60820015481499648, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "#CPC can't hv my nam", + "profile_sidebar_border_color": "9fa621", + "profile_background_tile": false, + "profile_sidebar_fill_color": "481802", + "created_at": "Wed Dec 30 21:15:44 +0000 2009", + "location": "Canada", + "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/1305144508\/DSCF0258_normal.JPG", + "profile_link_color": "ad5c34", + "is_translator": false, + "follow_request_sent": false, + "id_str": "100597465", + "contributors_enabled": false, + "default_profile": false, + "favourites_count": 5, + "url": null, + "utc_offset": -18000, + "id": 100597465, + "profile_use_background_image": true, + "listed_count": 20, + "lang": "en", + "protected": false, + "followers_count": 311, + "profile_text_color": "828282", + "profile_background_color": "000000", + "geo_enabled": false, + "time_zone": "Quito", + "description": "Fair-minded curious painter, news junkie, S**t Disturber. #tahrir supporter. Comet was my first dog. ", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a1.twimg.com\/profile_background_images\/63007779\/nvchocolate.br.jpg", + "default_profile_image": false, + "friends_count": 118, + "statuses_count": 7460, + "show_all_inline_media": false, + "screen_name": "CometsMum", + "following": false + }, + "source": "\u003Ca href=\"http:\/\/www.hootsuite.com\" rel=\"nofollow\"\u003EHootSuite\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + "id_str": "60820409276309504", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @CometsMum: RT @bigpicguy: @CBC just said the government was brought down bec opposition didn't like budget? HELLO CONTEMPT #elxn41 # ...", + "id": 60820409276309504, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Saskboy K.", + "profile_sidebar_border_color": "181A1E", + "profile_background_tile": false, + "profile_sidebar_fill_color": "252429", + "created_at": "Thu Jun 07 04:43:39 +0000 2007", + "location": "Saskatchewan, Canada", + "profile_image_url": "http:\/\/a1.twimg.com\/profile_images\/1174293129\/Img_6178riders_normal.jpg", + "profile_link_color": "2FC2EF", + "is_translator": false, + "follow_request_sent": false, + "id_str": "6634632", + "contributors_enabled": false, + "default_profile": false, + "favourites_count": 955, + "url": "http:\/\/www.abandonedstuff.com", + "utc_offset": -21600, + "id": 6634632, + "profile_use_background_image": true, + "listed_count": 57, + "lang": "en", + "protected": false, + "followers_count": 857, + "profile_text_color": "666666", + "profile_background_color": "1A1B1F", + "geo_enabled": false, + "time_zone": "Saskatchewan", + "description": "A Saskatchewan Blogger living in Regina", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a2.twimg.com\/profile_background_images\/10281422\/twitrgrid.JPG", + "default_profile_image": false, + "friends_count": 580, + "statuses_count": 5707, + "show_all_inline_media": false, + "screen_name": "saskboy", + "following": true + }, + "source": "web", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:29:31 +0000 2011", + "truncated": false, + "id_str": "60817395828277248", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "You're An Animal, Radio Collared: http:\/\/t.co\/sDalUAo iOS4 tracking file on your iPhone gives your secrets away.", + "id": 60817395828277248, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Saskboy K.", + "profile_sidebar_border_color": "181A1E", + "profile_background_tile": false, + "profile_sidebar_fill_color": "252429", + "created_at": "Thu Jun 07 04:43:39 +0000 2007", + "location": "Saskatchewan, Canada", + "profile_image_url": "http:\/\/a1.twimg.com\/profile_images\/1174293129\/Img_6178riders_normal.jpg", + "is_translator": false, + "follow_request_sent": false, + "profile_link_color": "2FC2EF", + "id_str": "6634632", + "favourites_count": 955, + "contributors_enabled": false, + "default_profile": false, + "url": "http:\/\/www.abandonedstuff.com", + "utc_offset": -21600, + "id": 6634632, + "listed_count": 57, + "profile_use_background_image": true, + "lang": "en", + "protected": false, + "followers_count": 857, + "profile_text_color": "666666", + "profile_background_color": "1A1B1F", + "geo_enabled": false, + "time_zone": "Saskatchewan", + "description": "A Saskatchewan Blogger living in Regina", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a2.twimg.com\/profile_background_images\/10281422\/twitrgrid.JPG", + "statuses_count": 5705, + "friends_count": 580, + "default_profile_image": false, + "show_all_inline_media": false, + "screen_name": "saskboy", + "following": true + }, + "source": "\u003Ca href=\"http:\/\/twitter.com\/tweetbutton\" rel=\"nofollow\"\u003ETweet Button\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:27:40 +0000 2011", + "truncated": false, + "id_str": "60816929274867712", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Watch now: http:\/\/ndp.ca\/hhuNZ Join me online for an #NDP Town Hall live from Thunder Bay. Be a part of it. #Cdnpoli #elxn41", + "id": 60816929274867712, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Jack Layton", + "profile_sidebar_border_color": "FF6600", + "profile_background_tile": false, + "profile_sidebar_fill_color": "FFFFFF", + "created_at": "Tue Jul 22 04:44:38 +0000 2008", + "location": "", + "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/1291903917\/Jack2_normal.jpg", + "follow_request_sent": null, + "profile_link_color": "FF6600", + "is_translator": false, + "id_str": "15526563", + "favourites_count": 0, + "contributors_enabled": false, + "default_profile": false, + "url": "http:\/\/www.ndp.ca", + "utc_offset": -18000, + "id": 15526563, + "profile_use_background_image": true, + "listed_count": 2673, + "lang": "en", + "protected": false, + "followers_count": 84617, + "profile_text_color": "000000", + "profile_background_color": "505052", + "time_zone": "Eastern Time (US & Canada)", + "description": "Leader, Canada's New Democrats.", + "notifications": null, + "geo_enabled": false, + "verified": false, + "profile_background_image_url": "http:\/\/a1.twimg.com\/profile_background_images\/224700669\/English.jpg", + "default_profile_image": false, + "friends_count": 11387, + "statuses_count": 892, + "screen_name": "jacklayton", + "following": null, + "show_all_inline_media": false + }, + "source": "\u003Ca href=\"http:\/\/www.hootsuite.com\" rel=\"nofollow\"\u003EHootSuite\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:14:50 +0000 2011", + "truncated": false, + "id_str": "60813699442675712", + "in_reply_to_user_id_str": "14738204", + "contributors": null, + "text": "@cbeust @tirsen SB Options is Guicified, supports .properties files and has a slightly neater API in my opinion.", + "id": 60813699442675712, + "retweet_count": 0, + "in_reply_to_status_id_str": "60723367321419776", + "geo": null, + "retweeted": false, + "in_reply_to_user_id": 14738204, + "in_reply_to_screen_name": "cbeust", + "place": null, + "user": { + "name": "Dhanji R. Prasanna", + "profile_sidebar_border_color": "eeeeee", + "profile_background_tile": true, + "profile_sidebar_fill_color": "efefef", + "created_at": "Mon Apr 28 01:03:24 +0000 2008", + "location": "Sydney, Australia", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/53414948\/dj_sp_normal.jpg", + "follow_request_sent": false, + "profile_link_color": "009999", + "is_translator": false, + "id_str": "14563623", + "favourites_count": 65, + "contributors_enabled": false, + "default_profile": false, + "url": "http:\/\/www.wideplay.com", + "utc_offset": 36000, + "id": 14563623, + "profile_use_background_image": true, + "listed_count": 141, + "lang": "en", + "protected": false, + "followers_count": 1271, + "profile_text_color": "333333", + "profile_background_color": "131516", + "time_zone": "Sydney", + "geo_enabled": true, + "description": "Senior Custodial Engineer at Google", + "notifications": true, + "verified": false, + "profile_background_image_url": "http:\/\/a2.twimg.com\/a\/1302724321\/images\/themes\/theme14\/bg.gif", + "default_profile_image": false, + "statuses_count": 6326, + "friends_count": 179, + "screen_name": "dhanji", + "following": true, + "show_all_inline_media": false + }, + "source": "\u003Ca href=\"http:\/\/itunes.apple.com\/us\/app\/twitter\/id409789998?mt=12\" rel=\"nofollow\"\u003ETwitter for Mac\u003C\/a\u003E", + "in_reply_to_status_id": 60723367321419776 + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:13:34 +0000 2011", + "truncated": false, + "id_str": "60813378960105473", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Gas Fracking\/Drilling Emergency in Bradford County - WNEP http:\/\/t.co\/7jEqBA5 Energy company destroying fresh water supply for locals", + "id": 60813378960105473, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Saskboy K.", + "profile_sidebar_border_color": "181A1E", + "profile_background_tile": false, + "profile_sidebar_fill_color": "252429", + "created_at": "Thu Jun 07 04:43:39 +0000 2007", + "location": "Saskatchewan, Canada", + "profile_image_url": "http:\/\/a1.twimg.com\/profile_images\/1174293129\/Img_6178riders_normal.jpg", + "follow_request_sent": false, + "profile_link_color": "2FC2EF", + "id_str": "6634632", + "is_translator": false, + "favourites_count": 955, + "contributors_enabled": false, + "default_profile": false, + "url": "http:\/\/www.abandonedstuff.com", + "utc_offset": -21600, + "id": 6634632, + "profile_use_background_image": true, + "listed_count": 57, + "lang": "en", + "protected": false, + "followers_count": 857, + "profile_text_color": "666666", + "profile_background_color": "1A1B1F", + "geo_enabled": false, + "time_zone": "Saskatchewan", + "description": "A Saskatchewan Blogger living in Regina", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a2.twimg.com\/profile_background_images\/10281422\/twitrgrid.JPG", + "default_profile_image": false, + "statuses_count": 5704, + "friends_count": 580, + "show_all_inline_media": false, + "screen_name": "saskboy", + "following": true + }, + "source": "\u003Ca href=\"http:\/\/twitter.com\/tweetbutton\" rel=\"nofollow\"\u003ETweet Button\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:04:43 +0000 2011", + "truncated": false, + "id_str": "60811152803897345", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Hudson CI 2.0.0 release candidate http:\/\/bit.ly\/eqJlZF (link to 37mb war file) <- available for early testing and feedback", + "id": 60811152803897345, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Stuart McCulloch", + "profile_sidebar_border_color": "C0DEED", + "profile_background_tile": false, + "profile_sidebar_fill_color": "DDEEF6", + "created_at": "Sun Sep 07 05:22:51 +0000 2008", + "location": "Kenilworth", + "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/59578326\/hmm_normal.png", + "profile_link_color": "0084B4", + "is_translator": false, + "follow_request_sent": false, + "id_str": "16166414", + "favourites_count": 0, + "contributors_enabled": false, + "default_profile": true, + "url": "http:\/\/mcculls.blogspot.com", + "utc_offset": 0, + "id": 16166414, + "profile_use_background_image": true, + "listed_count": 16, + "lang": "en", + "protected": false, + "followers_count": 227, + "profile_text_color": "333333", + "profile_background_color": "C0DEED", + "geo_enabled": false, + "time_zone": "London", + "description": "Three computers, two cats, one adorable wife", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a3.twimg.com\/a\/1303316982\/images\/themes\/theme1\/bg.png", + "default_profile_image": false, + "friends_count": 76, + "statuses_count": 1922, + "show_all_inline_media": false, + "screen_name": "mcculls", + "following": true + }, + "source": "web", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 21:02:04 +0000 2011", + "truncated": false, + "id_str": "60810484760322048", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Sure are a lot of tablets coming", + "id": 60810484760322048, + "retweet_count": 0, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Tim Bray", + "profile_sidebar_border_color": "87bc44", + "profile_background_tile": false, + "profile_sidebar_fill_color": "e0ff92", + "created_at": "Thu Mar 15 17:24:22 +0000 2007", + "location": "Vancouver", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/421637246\/Tim_normal.jpg", + "is_translator": false, + "follow_request_sent": false, + "profile_link_color": "AA0000", + "id_str": "1235521", + "favourites_count": 415, + "default_profile": false, + "contributors_enabled": false, + "url": "http:\/\/www.tbray.org\/ongoing\/", + "utc_offset": -28800, + "id": 1235521, + "profile_use_background_image": false, + "listed_count": 1634, + "lang": "en", + "protected": false, + "followers_count": 17072, + "profile_text_color": "000000", + "profile_background_color": "FFFFFF", + "time_zone": "Pacific Time (US & Canada)", + "description": "Web geek with a camera, currently doing Android stuff at Google.", + "notifications": false, + "geo_enabled": false, + "verified": false, + "friends_count": 669, + "profile_background_image_url": "http:\/\/a0.twimg.com\/profile_background_images\/1980852\/IMGP1686.jpg", + "default_profile_image": false, + "statuses_count": 8766, + "screen_name": "timbray", + "following": true, + "show_all_inline_media": false + }, + "source": "\u003Ca href=\"http:\/\/twitter.com\" rel=\"nofollow\"\u003ETweetie for Mac\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "truncated": true, + "created_at": "Wed Apr 20 20:53:32 +0000 2011", + "favorited": false, + "retweeted_status": { + "coordinates": null, + "truncated": false, + "created_at": "Wed Apr 20 14:23:18 +0000 2011", + "favorited": false, + "id_str": "60710135722549249", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "How plugins can support @hudsonci & @jenkinsci http:\/\/bit.ly\/haAe4L http:\/\/bit.ly\/heXINi thanks to @henriklynggaard for blogging this", + "id": 60710135722549249, + "in_reply_to_status_id_str": null, + "retweet_count": 2, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "place": null, + "user": { + "profile_sidebar_border_color": "C0DEED", + "name": "Hudson CI", + "profile_background_tile": true, + "profile_sidebar_fill_color": "DDEEF6", + "created_at": "Mon Jan 31 22:35:58 +0000 2011", + "location": "Everywhere!", + "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/1231444178\/hudson-twitter_normal.png", + "profile_link_color": "0084B4", + "is_translator": false, + "id_str": "245535216", + "follow_request_sent": false, + "contributors_enabled": false, + "default_profile": false, + "url": "http:\/\/hudson-ci.org", + "favourites_count": 0, + "utc_offset": -28800, + "id": 245535216, + "listed_count": 18, + "profile_use_background_image": true, + "followers_count": 334, + "lang": "en", + "protected": false, + "profile_text_color": "333333", + "time_zone": "Pacific Time (US & Canada)", + "geo_enabled": false, + "verified": false, + "profile_background_color": "C0DEED", + "description": "", + "notifications": false, + "friends_count": 14, + "statuses_count": 39, + "profile_background_image_url": "http:\/\/a1.twimg.com\/profile_background_images\/200487835\/hudson-twitter-background-logo.png", + "default_profile_image": false, + "screen_name": "hudsonci", + "show_all_inline_media": false, + "following": false + }, + "source": "web", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null + }, + "id_str": "60808339977797632", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @hudsonci: How plugins can support @hudsonci & @jenkinsci http:\/\/bit.ly\/haAe4L http:\/\/bit.ly\/heXINi thanks to @henriklynggaard for b ...", + "id": 60808339977797632, + "in_reply_to_status_id_str": null, + "retweet_count": 2, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "place": null, + "user": { + "profile_sidebar_border_color": "C0DEED", + "name": "Stuart McCulloch", + "profile_background_tile": false, + "profile_sidebar_fill_color": "DDEEF6", + "created_at": "Sun Sep 07 05:22:51 +0000 2008", + "location": "Kenilworth", + "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/59578326\/hmm_normal.png", + "profile_link_color": "0084B4", + "is_translator": false, + "id_str": "16166414", + "follow_request_sent": false, + "contributors_enabled": false, + "default_profile": true, + "url": "http:\/\/mcculls.blogspot.com", + "favourites_count": 0, + "utc_offset": 0, + "id": 16166414, + "listed_count": 16, + "profile_use_background_image": true, + "followers_count": 227, + "lang": "en", + "protected": false, + "profile_text_color": "333333", + "time_zone": "London", + "geo_enabled": false, + "verified": false, + "profile_background_color": "C0DEED", + "description": "Three computers, two cats, one adorable wife", + "notifications": false, + "friends_count": 76, + "statuses_count": 1921, + "profile_background_image_url": "http:\/\/a3.twimg.com\/a\/1303316982\/images\/themes\/theme1\/bg.png", + "default_profile_image": false, + "screen_name": "mcculls", + "show_all_inline_media": false, + "following": false + }, + "source": "web", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null + }, + { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 20 20:52:15 +0000 2011", + "truncated": true, + "retweeted_status": { + "coordinates": null, + "favorited": false, + "created_at": "Wed Apr 06 18:47:01 +0000 2011", + "truncated": false, + "id_str": "55703069324873728", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "Everybody thinks \"The Social Network\" is the best movie about forming a new startup, but they are wrong. The best movie is \"Ghostbusters\".", + "id": 55703069324873728, + "retweet_count": "100+", + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Patrick Ewing", + "profile_sidebar_border_color": "a1b44f", + "profile_background_tile": false, + "profile_sidebar_fill_color": "a0b34a", + "expanded_url": "http:\/\/patrickewing.info", + "created_at": "Sat Feb 24 18:13:15 +0000 2007", + "location": "Sane Francisco", + "profile_image_url": "http:\/\/a2.twimg.com\/profile_images\/1268256803\/cropped_normal.jpg", + "follow_request_sent": false, + "profile_link_color": "61351f", + "id_str": "792690", + "is_translator": false, + "contributors_enabled": false, + "default_profile": false, + "favourites_count": 1481, + "url": "http:\/\/t.co\/QXois9Q", + "utc_offset": -28800, + "id": 792690, + "profile_use_background_image": true, + "listed_count": 350, + "lang": "en", + "protected": false, + "followers_count": 51875, + "profile_text_color": "29230d", + "profile_background_color": "b2be63", + "time_zone": "Pacific Time (US & Canada)", + "geo_enabled": true, + "description": "Vector of enthusiasm. Tech lead on Twitter's Web Client team. I follow the code of Ruby, JavaScript & http:\/\/t.co\/vTmgS7L", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a2.twimg.com\/profile_background_images\/113507697\/cheerfulchirp_36_12284.jpg", + "statuses_count": 3791, + "default_profile_image": false, + "display_url": "patrickewing.info", + "friends_count": 765, + "screen_name": "hoverbird", + "show_all_inline_media": true, + "following": false + }, + "source": "\u003Ca href=\"http:\/\/itunes.apple.com\/us\/app\/twitter\/id409789998?mt=12\" rel=\"nofollow\"\u003ETwitter for Mac\u003C\/a\u003E", + "in_reply_to_status_id": null + }, + "id_str": "60808017754603520", + "in_reply_to_user_id_str": null, + "contributors": null, + "text": "RT @hoverbird: Everybody thinks \"The Social Network\" is the best movie about forming a new startup, but they are wrong. The best movie i ...", + "id": 60808017754603520, + "retweet_count": "100+", + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "in_reply_to_screen_name": null, + "place": null, + "user": { + "name": "Sarah Tidy", + "profile_sidebar_border_color": "C0DEED", + "profile_background_tile": false, + "profile_sidebar_fill_color": "DDEEF6", + "created_at": "Sun Nov 15 15:54:32 +0000 2009", + "location": "", + "profile_image_url": "http:\/\/a3.twimg.com\/profile_images\/1165642248\/jQa13bf7_normal", + "follow_request_sent": false, + "profile_link_color": "0084B4", + "id_str": "90187149", + "is_translator": false, + "contributors_enabled": false, + "default_profile": true, + "favourites_count": 7, + "url": null, + "utc_offset": null, + "id": 90187149, + "profile_use_background_image": true, + "listed_count": 0, + "lang": "en", + "protected": false, + "followers_count": 33, + "profile_text_color": "333333", + "profile_background_color": "C0DEED", + "time_zone": null, + "geo_enabled": false, + "description": "", + "notifications": false, + "verified": false, + "profile_background_image_url": "http:\/\/a3.twimg.com\/a\/1302646548\/images\/themes\/theme1\/bg.png", + "statuses_count": 112, + "default_profile_image": false, + "friends_count": 80, + "screen_name": "bu77er7ar7", + "show_all_inline_media": false, + "following": false + }, + "source": "\u003Ca href=\"http:\/\/mobile.twitter.com\" rel=\"nofollow\"\u003ETwitter for Android\u003C\/a\u003E", + "in_reply_to_status_id": null + } +] \ No newline at end of file