Skip to content

Commit 0e5ea23

Browse files
committed
support bind to existing
1 parent f74450c commit 0e5ea23

8 files changed

Lines changed: 172 additions & 26 deletions

File tree

src/main/java/com/jsoniter/Codegen.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ private static String genMap(Class clazz, Type[] typeArgs) {
114114
}
115115
StringBuilder lines = new StringBuilder();
116116
append(lines, "public static Object decode_(com.jsoniter.JsonIterator iter) {");
117-
append(lines, "{{clazz}} map = new {{clazz}}();");
117+
append(lines, "{{clazz}} map = ({{clazz}})com.jsoniter.CodegenAccess.resetExistingObject(iter);");
118+
append(lines, "if (map == null) { map = new {{clazz}}(); }");
118119
append(lines, "for (String field = iter.readObject(); field != null; field = iter.readObject()) {");
119120
append(lines, "map.put(field, {{op}});");
120121
append(lines, "}");

src/main/java/com/jsoniter/CodegenAccess.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
11
package com.jsoniter;
22

33
import java.io.IOException;
4+
import java.util.Collection;
45

56
// only uesd by generated code to access decoder
67
public class CodegenAccess {
78

9+
public static <T extends Collection> T reuseCollection(T col) {
10+
col.clear();
11+
return col;
12+
}
13+
14+
public static Object existingObject(JsonIterator iter) {
15+
return iter.existingObject;
16+
}
17+
18+
public static Object resetExistingObject(JsonIterator iter) {
19+
Object obj = iter.existingObject;
20+
iter.existingObject = null;
21+
return obj;
22+
}
23+
24+
public static void setExistingObject(JsonIterator iter, Object obj) {
25+
iter.existingObject = obj;
26+
}
27+
828
public static byte nextToken(JsonIterator iter) throws IOException {
929
return iter.nextToken();
1030
}
@@ -53,7 +73,7 @@ public static boolean readArrayStart(JsonIterator iter) throws IOException {
5373
public static boolean readObjectStart(JsonIterator iter) throws IOException {
5474
byte c = iter.nextToken();
5575
if (c != '{') {
56-
throw iter.reportError("readObjectStart", "expect { or n, found: " + (char)c);
76+
throw iter.reportError("readObjectStart", "expect { or n, found: " + (char) c);
5777
}
5878
c = iter.nextToken();
5979
if (c == '}') {
@@ -79,7 +99,7 @@ public static final int readObjectFieldAsHash(JsonIterator iter) throws IOExcept
7999
for (; ; ) {
80100
byte c = 0;
81101
int i = iter.head;
82-
for ( ;i < iter.tail; i++) {
102+
for (; i < iter.tail; i++) {
83103
c = iter.buf[i];
84104
if (c == '"') {
85105
break;

src/main/java/com/jsoniter/CodegenImplArray.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,32 +85,34 @@ public static String genCollection(Class clazz, Type[] typeArgs) {
8585
private static String genCollectionWithCapacity(Class clazz, Type compType) {
8686
StringBuilder lines = new StringBuilder();
8787
append(lines, "public static Object decode_(com.jsoniter.JsonIterator iter) {");
88+
append(lines, "if (iter.readNull()) { return null; }");
89+
append(lines, "{{clazz}} col = ({{clazz}})com.jsoniter.CodegenAccess.resetExistingObject(iter);");
8890
append(lines, "if (!com.jsoniter.CodegenAccess.readArrayStart(iter)) {");
89-
append(lines, "return new {{clazz}}(0);");
91+
append(lines, "return col == null ? new {{clazz}}(0): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
9092
append(lines, "}");
9193
append(lines, "Object a1 = {{op}};");
9294
append(lines, "if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {");
93-
append(lines, "{{clazz}} obj = new {{clazz}}(1);");
95+
append(lines, "{{clazz}} obj = col == null ? new {{clazz}}(1): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
9496
append(lines, "obj.add(a1);");
9597
append(lines, "return obj;");
9698
append(lines, "}");
9799
append(lines, "Object a2 = {{op}};");
98100
append(lines, "if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {");
99-
append(lines, "{{clazz}} obj = new {{clazz}}(2);");
101+
append(lines, "{{clazz}} obj = col == null ? new {{clazz}}(2): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
100102
append(lines, "obj.add(a1);");
101103
append(lines, "obj.add(a2);");
102104
append(lines, "return obj;");
103105
append(lines, "}");
104106
append(lines, "Object a3 = {{op}};");
105107
append(lines, "if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {");
106-
append(lines, "{{clazz}} obj = new {{clazz}}(3);");
108+
append(lines, "{{clazz}} obj = col == null ? new {{clazz}}(3): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
107109
append(lines, "obj.add(a1);");
108110
append(lines, "obj.add(a2);");
109111
append(lines, "obj.add(a3);");
110112
append(lines, "return obj;");
111113
append(lines, "}");
112114
append(lines, "Object a4 = {{op}};");
113-
append(lines, "{{clazz}} obj = new {{clazz}}(110);");
115+
append(lines, "{{clazz}} obj = col == null ? new {{clazz}}(8): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
114116
append(lines, "obj.add(a1);");
115117
append(lines, "obj.add(a2);");
116118
append(lines, "obj.add(a3);");
@@ -129,32 +131,34 @@ private static String genCollectionWithCapacity(Class clazz, Type compType) {
129131
private static String genCollectionWithoutCapacity(Class clazz, Type compType) {
130132
StringBuilder lines = new StringBuilder();
131133
append(lines, "public static Object decode_(com.jsoniter.JsonIterator iter) {");
134+
append(lines, "if (iter.readNull()) { return null; }");
135+
append(lines, "{{clazz}} col = ({{clazz}})com.jsoniter.CodegenAccess.resetExistingObject(iter);");
132136
append(lines, "if (!com.jsoniter.CodegenAccess.readArrayStart(iter)) {");
133-
append(lines, "return new {{clazz}}();");
137+
append(lines, "return col == null ? new {{clazz}}(): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
134138
append(lines, "}");
135139
append(lines, "Object a1 = {{op}};");
136140
append(lines, "if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {");
137-
append(lines, "{{clazz}} obj = new {{clazz}}();");
141+
append(lines, "{{clazz}} obj = col == null ? new {{clazz}}(): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
138142
append(lines, "obj.add(a1);");
139143
append(lines, "return obj;");
140144
append(lines, "}");
141145
append(lines, "Object a2 = {{op}};");
142146
append(lines, "if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {");
143-
append(lines, "{{clazz}} obj = new {{clazz}}();");
147+
append(lines, "{{clazz}} obj = col == null ? new {{clazz}}(): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
144148
append(lines, "obj.add(a1);");
145149
append(lines, "obj.add(a2);");
146150
append(lines, "return obj;");
147151
append(lines, "}");
148152
append(lines, "Object a3 = {{op}};");
149153
append(lines, "if (com.jsoniter.CodegenAccess.nextToken(iter) != ',') {");
150-
append(lines, "{{clazz}} obj = new {{clazz}}();");
154+
append(lines, "{{clazz}} obj = col == null ? new {{clazz}}(): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
151155
append(lines, "obj.add(a1);");
152156
append(lines, "obj.add(a2);");
153157
append(lines, "obj.add(a3);");
154158
append(lines, "return obj;");
155159
append(lines, "}");
156160
append(lines, "Object a4 = {{op}};");
157-
append(lines, "{{clazz}} obj = new {{clazz}}();");
161+
append(lines, "{{clazz}} obj = col == null ? new {{clazz}}(): ({{clazz}})com.jsoniter.CodegenAccess.reuseCollection(col);");
158162
append(lines, "obj.add(a1);");
159163
append(lines, "obj.add(a2);");
160164
append(lines, "obj.add(a3);");

src/main/java/com/jsoniter/CodegenImplNative.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,12 @@ private static void append(StringBuilder lines, String str) {
171171
lines.append(str);
172172
lines.append("\n");
173173
}
174+
175+
public static boolean isNative(Type valueType) {
176+
if (valueType instanceof Class) {
177+
Class clazz = (Class) valueType;
178+
return NATIVE_READS.containsKey(clazz.getCanonicalName());
179+
}
180+
return false;
181+
}
174182
}

src/main/java/com/jsoniter/CodegenImplObject.java

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.jsoniter;
22

3+
import java.lang.reflect.Type;
34
import java.util.*;
45

56
class CodegenImplObject {
@@ -46,14 +47,22 @@ public static String genObjectUsingSlice(Class clazz, String cacheKey, Customize
4647
appendVarDef(lines, param);
4748
}
4849
}
50+
append(lines, "com.jsoniter.CodegenAccess.resetExistingObject(iter);");
4951
append(lines, "com.jsoniter.Slice field = com.jsoniter.CodegenAccess.readObjectFieldAsSlice(iter);");
5052
append(lines, "boolean once = true;");
5153
append(lines, "while (once) {");
5254
append(lines, "once = false;");
5355
append(lines, "switch (field.len) {");
5456
String rendered = renderTriTree(cacheKey, trieTree);
5557
for (Binding field : fields) {
56-
rendered = rendered.replace("_" + field.name + "_", "obj." + field.name);
58+
if (ctor.parameters.isEmpty() && fields.contains(field)) {
59+
if (shouldReuseObject(field.valueType)) {
60+
rendered = rendered.replace("_" + field.name + "_", String.format("obj.%s", field.name));
61+
} else {
62+
rendered = rendered.replace("_" + field.name + "_", String.format(
63+
"com.jsoniter.CodegenAccess.setExistingObject(iter, obj.%s);\nobj.%s", field.name, field.name));
64+
}
65+
}
5766
}
5867
append(lines, rendered);
5968
append(lines, "}"); // end of switch
@@ -187,6 +196,7 @@ public static String genObjectUsingHash(Class clazz, String cacheKey, Customized
187196
appendVarDef(lines, param);
188197
}
189198
}
199+
append(lines, "com.jsoniter.CodegenAccess.resetExistingObject(iter);");
190200
append(lines, "switch (com.jsoniter.CodegenAccess.readObjectFieldAsHash(iter)) {");
191201
HashSet<Integer> knownHashes = new HashSet<Integer>();
192202
for (Binding field : allBindings) {
@@ -207,11 +217,7 @@ public static String genObjectUsingHash(Class clazz, String cacheKey, Customized
207217
}
208218
knownHashes.add(intHash);
209219
append(lines, "case " + intHash + ": ");
210-
if (ctor.parameters.isEmpty() && fields.contains(field)) {
211-
append(lines, String.format("obj.%s = %s;", field.name, CodegenImplNative.genField(field, cacheKey)));
212-
} else {
213-
append(lines, String.format("_%s_ = %s;", field.name, CodegenImplNative.genField(field, cacheKey)));
214-
}
220+
appendFieldSet(lines, cacheKey, ctor, fields, field);
215221
append(lines, "break;");
216222
}
217223
}
@@ -229,11 +235,7 @@ public static String genObjectUsingHash(Class clazz, String cacheKey, Customized
229235
}
230236
int intHash = (int) hash;
231237
append(lines, "case " + intHash + ": ");
232-
if (ctor.parameters.isEmpty() && fields.contains(field)) {
233-
append(lines, String.format("obj.%s = %s;", field.name, CodegenImplNative.genField(field, cacheKey)));
234-
} else {
235-
append(lines, String.format("_%s_ = %s;", field.name, CodegenImplNative.genField(field, cacheKey)));
236-
}
238+
appendFieldSet(lines, cacheKey, ctor, fields, field);
237239
append(lines, "continue;");
238240
}
239241
}
@@ -254,6 +256,17 @@ public static String genObjectUsingHash(Class clazz, String cacheKey, Customized
254256
.replace("{{newInst}}", genNewInstCode(clazz, ctor));
255257
}
256258

259+
private static void appendFieldSet(StringBuilder lines, String cacheKey, CustomizedConstructor ctor, List<Binding> fields, Binding field) {
260+
if (ctor.parameters.isEmpty() && fields.contains(field)) {
261+
if (!shouldReuseObject(field.valueType)) {
262+
append(lines, String.format("com.jsoniter.CodegenAccess.setExistingObject(iter, obj.%s);", field.name));
263+
}
264+
append(lines, String.format("obj.%s = %s;", field.name, CodegenImplNative.genField(field, cacheKey)));
265+
} else {
266+
append(lines, String.format("_%s_ = %s;", field.name, CodegenImplNative.genField(field, cacheKey)));
267+
}
268+
}
269+
257270
private static void appendSetter(List<CustomizedSetter> setters, StringBuilder lines) {
258271
for (CustomizedSetter setter : setters) {
259272
lines.append("obj.");
@@ -283,13 +296,21 @@ private static String genObjectUsingSkip(Class clazz, CustomizedConstructor ctor
283296

284297
private static String genNewInstCode(Class clazz, CustomizedConstructor ctor) {
285298
StringBuilder code = new StringBuilder();
299+
if (ctor.parameters.isEmpty()) {
300+
// nothing to bind, safe to reuse existing object
301+
code.append("(com.jsoniter.CodegenAccess.existingObject(iter) == null ? ");
302+
}
286303
if (ctor.staticMethodName == null) {
287304
code.append(String.format("new %s", clazz.getCanonicalName()));
288305
} else {
289306
code.append(String.format("%s.%s", clazz.getCanonicalName(), ctor.staticMethodName));
290307
}
291308
List<Binding> params = ctor.parameters;
292309
appendInvocation(code, params);
310+
if (ctor.parameters.isEmpty()) {
311+
// nothing to bind, safe to reuse existing object
312+
code.append(String.format(" : (%s)com.jsoniter.CodegenAccess.existingObject(iter))", clazz.getCanonicalName()));
313+
}
293314
return code.toString();
294315
}
295316

@@ -311,4 +332,14 @@ private static void append(StringBuilder lines, String str) {
311332
lines.append(str);
312333
lines.append("\n");
313334
}
335+
336+
public static boolean shouldReuseObject(Type valueType) {
337+
if (valueType instanceof Class) {
338+
Class clazz = (Class) valueType;
339+
if (clazz.isArray()) {
340+
return false;
341+
}
342+
}
343+
return CodegenImplNative.isNative(valueType);
344+
}
314345
}

src/main/java/com/jsoniter/JsonIterator.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class JsonIterator implements Closeable {
2020
boolean eof;
2121
final Slice reusableSlice = new Slice(null, 0, 0);
2222
char[] reusableChars = new char[32];
23+
Object existingObject = null; // the object should be bind to next
2324

2425
static {
2526
for (int i = 0; i < valueTypes.length; i++) {
@@ -315,13 +316,23 @@ public final Object readAnyObject() throws IOException {
315316
}
316317
}
317318

319+
public final <T> T read(T existingObject) throws IOException {
320+
this.existingObject = existingObject;
321+
Class<?> clazz = existingObject.getClass();
322+
return (T) Codegen.getDecoder(TypeLiteral.generateCacheKey(clazz), clazz).decode(this);
323+
}
324+
325+
public final <T> T read(TypeLiteral<T> typeLiteral, T existingObject) throws IOException {
326+
this.existingObject = existingObject;
327+
return (T) Codegen.getDecoder(typeLiteral.cacheKey, typeLiteral.getType()).decode(this);
328+
}
329+
318330
public final <T> T read(Class<T> clazz) throws IOException {
319331
return (T) Codegen.getDecoder(TypeLiteral.generateCacheKey(clazz), clazz).decode(this);
320332
}
321333

322334
public final <T> T read(TypeLiteral<T> typeLiteral) throws IOException {
323-
Type type = typeLiteral.getType();
324-
return (T) Codegen.getDecoder(typeLiteral.cacheKey, type).decode(this);
335+
return (T) Codegen.getDecoder(typeLiteral.cacheKey, typeLiteral.getType()).decode(this);
325336
}
326337

327338
public ValueType whatIsNext() throws IOException {

src/main/java/com/jsoniter/TypeLiteral.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ public static String generateCacheKey(Type type) {
2424
StringBuilder decoderClassName = new StringBuilder("codegen.");
2525
if (type instanceof Class) {
2626
Class clazz = (Class) type;
27+
if (clazz.isAnonymousClass()) {
28+
throw new RuntimeException("anonymous class not supported: " + clazz);
29+
}
2730
decoderClassName.append(clazz.getCanonicalName().replace("[]", "_array"));
2831
} else if (type instanceof ParameterizedType) {
2932
ParameterizedType pType = (ParameterizedType) type;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.jsoniter;
2+
3+
import junit.framework.TestCase;
4+
5+
import java.io.IOException;
6+
import java.util.ArrayList;
7+
import java.util.HashMap;
8+
import java.util.LinkedList;
9+
import java.util.List;
10+
11+
public class TestExisting extends TestCase {
12+
13+
static {
14+
// JsonIterator.enableStrictMode();
15+
}
16+
17+
public static class TestObj1 {
18+
public String field1;
19+
public String field2;
20+
}
21+
22+
public void test_direct_reuse() throws IOException {
23+
TestObj1 testObj = new TestObj1();
24+
testObj.field2 = "world";
25+
JsonIterator iter = JsonIterator.parse("{ 'field1' : 'hello' }".replace('\'', '"'));
26+
testObj = iter.read(testObj);
27+
assertEquals("hello", testObj.field1);
28+
assertEquals("world", testObj.field2);
29+
}
30+
31+
public static class TestObj2 {
32+
public String field3;
33+
public TestObj1 field4;
34+
}
35+
36+
public void test_indirect_reuse() throws IOException {
37+
TestObj2 testObj = new TestObj2();
38+
testObj.field4 = new TestObj1();
39+
testObj.field4.field1 = "world";
40+
JsonIterator iter = JsonIterator.parse("{ 'field3' : 'hello', 'field4': {'field2': 'hello'} }".replace('\'', '"'));
41+
testObj = iter.read(testObj);
42+
assertEquals("hello", testObj.field3);
43+
assertEquals("hello", testObj.field4.field2);
44+
assertEquals("world", testObj.field4.field1);
45+
}
46+
47+
public void test_reuse_list() throws IOException {
48+
List list1 = new ArrayList();
49+
JsonIterator iter = JsonIterator.parse("[1]");
50+
List list2= iter.read(new TypeLiteral<List<Integer>>(){}, list1);
51+
assertEquals(System.identityHashCode(list2), System.identityHashCode(list1));
52+
}
53+
54+
public void test_reuse_linked_list() throws IOException {
55+
LinkedList list1 = new LinkedList();
56+
JsonIterator iter = JsonIterator.parse("[1]");
57+
List list2= iter.read(new TypeLiteral<LinkedList<Integer>>(){}, list1);
58+
assertEquals(System.identityHashCode(list2), System.identityHashCode(list1));
59+
}
60+
61+
public void test_reuse_map() throws IOException {
62+
JsonIterator iter = JsonIterator.parse("{ 'field1' : 'hello' }".replace('\'', '"'));
63+
HashMap<String, Object> map1 = new HashMap<String, Object>();
64+
map1.put("a", "b");
65+
HashMap<String, Object> map2 = iter.read(map1);
66+
assertEquals("b", map2.get("a"));
67+
}
68+
}

0 commit comments

Comments
 (0)