Implemented #138
Tatu
10 years ago
3 | 3 | #121: Increase size of low-level byte[]/char[] input/output buffers |
4 | 4 | (from 4k->8k for bytes, 2k->4k for chars) |
5 | 5 | #127: Add `JsonGenerator.writeStartArray(int size)` for binary formats |
6 | #138: Add support for using `char[]` as input source; optimize handling | |
7 | of `String` input as well. | |
6 | 8 | - Refactor `BufferRecycler` to eliminate helper enums |
7 | 9 | |
8 | 10 | ------------------------------------------------------------------------ |
42 | 42 | java.io.Serializable // since 2.1 (for Android, mostly) |
43 | 43 | { |
44 | 44 | /** |
45 | * Computed for Jackson 2.3.0 release | |
46 | */ | |
47 | private static final long serialVersionUID = 3194418244231611666L; | |
45 | * Computed for Jackson 2.4.0 release | |
46 | */ | |
47 | private static final long serialVersionUID = 3306684576057132431L; | |
48 | 48 | |
49 | 49 | /* |
50 | 50 | /********************************************************** |
809 | 809 | * @since 2.1 |
810 | 810 | */ |
811 | 811 | public JsonParser createParser(String content) throws IOException, JsonParseException { |
812 | Reader r = new StringReader(content); | |
813 | // true -> we own the Reader (and must close); not a big deal | |
814 | IOContext ctxt = _createContext(r, true); | |
815 | // [JACKSON-512]: allow wrapping with InputDecorator | |
816 | if (_inputDecorator != null) { | |
817 | r = _inputDecorator.decorate(ctxt, r); | |
818 | } | |
819 | return _createParser(r, ctxt); | |
812 | final int strLen = content.length(); | |
813 | // Actually, let's use this for medium-sized content, up to 64kB chunk (32kb char) | |
814 | if (_inputDecorator != null || strLen > 0x8000) { | |
815 | // easier to just wrap in a Reader than extend InputDecorator; or, if content | |
816 | // is too long for us to copy it over | |
817 | return createParser(new StringReader(content)); | |
818 | } | |
819 | IOContext ctxt = _createContext(content, true); | |
820 | char[] buf = ctxt.allocTokenBuffer(); | |
821 | if (buf.length < strLen) { // sanity check; should never occur | |
822 | buf = new char[strLen]; | |
823 | } | |
824 | content.getChars(0, strLen, buf, 0); | |
825 | return _createParser(buf, 0, strLen, ctxt, true); | |
826 | } | |
827 | ||
828 | /** | |
829 | * Method for constructing parser for parsing | |
830 | * contents of given char array. | |
831 | * | |
832 | * @since 2.4 | |
833 | */ | |
834 | public JsonParser createParser(char[] content) throws IOException { | |
835 | return createParser(content, 0, content.length); | |
836 | } | |
837 | ||
838 | /** | |
839 | * Method for constructing parser for parsing | |
840 | * contents of given char array. | |
841 | * | |
842 | * @since 2.4 | |
843 | */ | |
844 | public JsonParser createParser(char[] content, int offset, int len) throws IOException { | |
845 | if (_inputDecorator != null) { // easier to just wrap in a Reader than extend InputDecorator | |
846 | return createParser(new CharArrayReader(content, offset, len)); | |
847 | } | |
848 | return _createParser(content, offset, len, _createContext(content, true), false); | |
820 | 849 | } |
821 | 850 | |
822 | 851 | /* |
1198 | 1227 | |
1199 | 1228 | /** |
1200 | 1229 | * Overridable factory method that actually instantiates parser |
1230 | * using given <code>char[]</code> object for accessing content. | |
1231 | * | |
1232 | * @since 2.4 | |
1233 | */ | |
1234 | protected JsonParser _createParser(char[] data, int offset, int len, IOContext ctxt, | |
1235 | boolean recyclable) throws IOException { | |
1236 | return new ReaderBasedJsonParser(ctxt, _parserFeatures, null, _objectCodec, | |
1237 | _rootCharSymbols.makeChild(isEnabled(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES), | |
1238 | isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES)), | |
1239 | data, offset, offset+len, | |
1240 | // false -> caller-provided, not handled by BufferRecycler | |
1241 | recyclable); | |
1242 | } | |
1243 | ||
1244 | /** | |
1245 | * Overridable factory method that actually instantiates parser | |
1201 | 1246 | * using given {@link Reader} object for reading content |
1202 | 1247 | * passed as raw byte array. |
1203 | 1248 | *<p> |
1207 | 1252 | * interface from sub-class perspective, although not a public |
1208 | 1253 | * method available to users of factory implementations. |
1209 | 1254 | */ |
1210 | protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException, JsonParseException | |
1255 | protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException | |
1211 | 1256 | { |
1212 | 1257 | return new ByteSourceJsonBootstrapper(ctxt, data, offset, len).constructParser(_parserFeatures, |
1213 | 1258 | _objectCodec, _rootByteSymbols, _rootCharSymbols, |
219 | 219 | */ |
220 | 220 | if (canonicalize) { |
221 | 221 | BytesToNameCanonicalizer can = rootByteSymbols.makeChild(canonicalize, intern); |
222 | return new UTF8StreamJsonParser(_context, parserFeatures, _in, codec, can, _inputBuffer, _inputPtr, _inputEnd, _bufferRecyclable); | |
222 | return new UTF8StreamJsonParser(_context, parserFeatures, _in, codec, can, | |
223 | _inputBuffer, _inputPtr, _inputEnd, _bufferRecyclable); | |
223 | 224 | } |
224 | 225 | } |
225 | 226 | return new ReaderBasedJsonParser(_context, parserFeatures, constructReader(), codec, |
44 | 44 | */ |
45 | 45 | protected char[] _inputBuffer; |
46 | 46 | |
47 | /** | |
48 | * Flag that indicates whether the input buffer is recycable (and | |
49 | * needs to be returned to recycler once we are done) or not. | |
50 | *<p> | |
51 | * If it is not, it also means that parser can NOT modify underlying | |
52 | * buffer. | |
53 | */ | |
54 | protected boolean _bufferRecyclable; | |
55 | ||
47 | 56 | /* |
48 | 57 | /********************************************************** |
49 | 58 | /* Configuration |
68 | 77 | * some access (or skipped to obtain the next token) |
69 | 78 | */ |
70 | 79 | protected boolean _tokenIncomplete = false; |
71 | ||
80 | ||
72 | 81 | /* |
73 | 82 | /********************************************************** |
74 | 83 | /* Life-cycle |
75 | 84 | /********************************************************** |
76 | 85 | */ |
77 | 86 | |
87 | /** | |
88 | * Method called when caller wants to provide input buffer directly, | |
89 | * and it may or may not be recyclable use standard recycle context. | |
90 | * | |
91 | * @since 2.4 | |
92 | */ | |
78 | 93 | public ReaderBasedJsonParser(IOContext ctxt, int features, Reader r, |
79 | ObjectCodec codec, CharsToNameCanonicalizer st) | |
94 | ObjectCodec codec, CharsToNameCanonicalizer st, | |
95 | char[] inputBuffer, int start, int end, | |
96 | boolean bufferRecyclable) | |
97 | { | |
98 | super(ctxt, features); | |
99 | _reader = r; | |
100 | _inputBuffer = inputBuffer; | |
101 | _inputPtr = start; | |
102 | _inputEnd = end; | |
103 | _objectCodec = codec; | |
104 | _symbols = st; | |
105 | _hashSeed = st.hashSeed(); | |
106 | _bufferRecyclable = bufferRecyclable; | |
107 | } | |
108 | ||
109 | /** | |
110 | * Method called when input comes as a {@link java.io.Reader}, and buffer allocation | |
111 | * can be done using default mechanism. | |
112 | */ | |
113 | public ReaderBasedJsonParser(IOContext ctxt, int features, Reader r, | |
114 | ObjectCodec codec, CharsToNameCanonicalizer st) | |
80 | 115 | { |
81 | 116 | super(ctxt, features); |
82 | 117 | _reader = r; |
83 | 118 | _inputBuffer = ctxt.allocTokenBuffer(); |
119 | _inputPtr = 0; | |
120 | _inputEnd = 0; | |
84 | 121 | _objectCodec = codec; |
85 | 122 | _symbols = st; |
86 | 123 | _hashSeed = st.hashSeed(); |
87 | } | |
88 | ||
124 | _bufferRecyclable = false; | |
125 | } | |
126 | ||
89 | 127 | /* |
90 | 128 | /********************************************************** |
91 | 129 | /* Base method defs, overrides |
165 | 203 | super._releaseBuffers(); |
166 | 204 | // merge new symbols, if any |
167 | 205 | _symbols.release(); |
168 | char[] buf = _inputBuffer; | |
169 | if (buf != null) { | |
170 | _inputBuffer = null; | |
171 | _ioContext.releaseTokenBuffer(buf); | |
206 | // and release buffers, if they are recyclable ones | |
207 | if (_bufferRecyclable) { | |
208 | char[] buf = _inputBuffer; | |
209 | if (buf != null) { | |
210 | _inputBuffer = null; | |
211 | _ioContext.releaseTokenBuffer(buf); | |
212 | } | |
172 | 213 | } |
173 | 214 | } |
174 | 215 |
12 | 12 | public void testCoreVersions() throws Exception |
13 | 13 | { |
14 | 14 | assertVersion(new JsonFactory().version()); |
15 | JsonParser jp = new ReaderBasedJsonParser(getIOContext(), 0, null, null, | |
15 | ReaderBasedJsonParser jp = new ReaderBasedJsonParser(getIOContext(), 0, null, null, | |
16 | 16 | CharsToNameCanonicalizer.createRoot()); |
17 | 17 | assertVersion(jp.version()); |
18 | 18 | jp.close(); |
296 | 296 | * correctly; mostly to stress-test underlying segment-based |
297 | 297 | * text buffer(s). |
298 | 298 | */ |
299 | public void testLongText() throws Exception | |
300 | { | |
301 | JsonFactory jf = new JsonFactory(); | |
302 | // lengths chosen to tease out problems with buffer allocation... | |
303 | _testLongText(jf, 7700); | |
304 | _testLongText(jf, 49000); | |
305 | _testLongText(jf, 96000); | |
306 | } | |
307 | ||
299 | 308 | @SuppressWarnings("resource") |
300 | public void testLongText() throws Exception | |
301 | { | |
302 | final int LEN = 96000; | |
309 | private void _testLongText(JsonFactory jf, int LEN) throws Exception | |
310 | { | |
303 | 311 | StringBuilder sb = new StringBuilder(LEN + 100); |
304 | 312 | Random r = new Random(99); |
305 | 313 | while (sb.length() < LEN) { |
324 | 332 | } |
325 | 333 | } |
326 | 334 | final String VALUE = sb.toString(); |
327 | ||
328 | JsonFactory jf = new JsonFactory(); | |
329 | 335 | |
330 | // Let's use real generator to get json done right | |
336 | // Let's use real generator to get JSON done right | |
331 | 337 | StringWriter sw = new StringWriter(LEN + (LEN >> 2)); |
332 | 338 | JsonGenerator jg = jf.createGenerator(sw); |
333 | 339 | jg.writeStartObject(); |
38 | 38 | final String jsonStr = input.asJsonString(f); |
39 | 39 | final byte[] json = jsonStr.getBytes("UTF-8"); |
40 | 40 | |
41 | new ManualReadPerfWithMedia(f, jsonStr).test("Reader", "char[]", json.length); | |
41 | new ManualReadPerfWithMedia(f, jsonStr).test("String", "char[]", json.length); | |
42 | 42 | } |
43 | 43 | |
44 | 44 | protected void testRead1(int reps) throws Exception |
45 | 45 | { |
46 | final String input = _json; | |
47 | 46 | while (--reps >= 0) { |
48 | JsonParser p = _factory.createParser(input); | |
47 | JsonParser p = _factory.createParser(_json); | |
49 | 48 | _stream(p); |
50 | 49 | p.close(); |
51 | 50 | } |
53 | 52 | |
54 | 53 | protected void testRead2(int reps) throws Exception |
55 | 54 | { |
56 | final String input = _json; | |
55 | final char[] ch = _json.toCharArray(); | |
57 | 56 | while (--reps >= 0) { |
58 | /* | |
59 | final char[] ch = input.toCharArray(); | |
60 | 57 | JsonParser p = _factory.createParser(ch, 0, ch.length); |
61 | */ | |
62 | JsonParser p = _factory.createParser(input); | |
63 | 58 | _stream(p); |
64 | 59 | p.close(); |
65 | 60 | } |