Skip to content

Commit df185fe

Browse files
FireMasterKStypox
authored andcommitted
Buffer Pool, UTF Surrogate and test fixes.
1 parent 68a1285 commit df185fe

File tree

10 files changed

+1504
-1227
lines changed

10 files changed

+1504
-1227
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@
174174
<configuration>
175175
<debug>true</debug>
176176
<debuglevel>none</debuglevel>
177-
<release>8</release>
177+
<release>17</release>
178178
<compilerArgument>-Xlint:all</compilerArgument>
179179
<compilerArguments>
180180
<Werror />
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2011 The nanojson Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.grack.nanojson;
17+
18+
import java.nio.CharBuffer;
19+
import java.util.PriorityQueue;
20+
import java.util.concurrent.atomic.AtomicInteger;
21+
22+
public final class CharBufferPool {
23+
24+
private static final AtomicInteger SIZE = new AtomicInteger(0);
25+
private static final int MAX_SIZE = 1000;
26+
private static final int MAX_RETAINED_BUFFER_SIZE = 16 * 1024; // 16KB limit for pooled buffers
27+
28+
private static final PriorityQueue<CharBuffer> BUFFERS = new PriorityQueue<>();
29+
30+
private CharBufferPool() {
31+
}
32+
33+
public static CharBuffer get(int capacity) {
34+
synchronized (BUFFERS) {
35+
if (!BUFFERS.isEmpty()) {
36+
CharBuffer buffer = BUFFERS.poll();
37+
if (buffer.capacity() < capacity) {
38+
return CharBuffer.allocate(capacity);
39+
}
40+
return buffer;
41+
}
42+
}
43+
44+
if (SIZE.incrementAndGet() > MAX_SIZE) {
45+
SIZE.decrementAndGet();
46+
throw new IllegalStateException("Buffer pool size limit exceeded");
47+
}
48+
49+
return CharBuffer.allocate(capacity);
50+
}
51+
52+
public static void release(CharBuffer buffer) {
53+
if (buffer == null || buffer.capacity() <= 0) {
54+
return;
55+
}
56+
57+
if (buffer.limit() > MAX_RETAINED_BUFFER_SIZE) {
58+
// If the buffer is too large, decrement SIZE so another can be created
59+
SIZE.decrementAndGet();
60+
return;
61+
}
62+
63+
buffer.clear();
64+
65+
synchronized (BUFFERS) {
66+
BUFFERS.add(buffer);
67+
}
68+
}
69+
}

src/main/java/com/grack/nanojson/JsonParser.java

Lines changed: 93 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
/**
2929
* Simple JSON parser.
30-
*
30+
*
3131
* <pre>
3232
* Object json = {@link JsonParser}.any().from("{\"a\":[true,false], \"b\":1}");
3333
* Number json = ({@link Number}){@link JsonParser}.any().from("123.456e7");
@@ -44,7 +44,8 @@ public final class JsonParser {
4444
private final boolean lazyStrings;
4545

4646
/**
47-
* Returns a type-safe parser context for a {@link JsonObject}, {@link JsonArray} or "any" type from which you can
47+
* Returns a type-safe parser context for a {@link JsonObject},
48+
* {@link JsonArray} or "any" type from which you can
4849
* parse a {@link String} or a {@link Reader}.
4950
*
5051
* @param <T> The parsed type.
@@ -104,7 +105,8 @@ public T from(URL url) throws JsonParserException {
104105
}
105106

106107
/**
107-
* Parses the current JSON type from a {@link InputStream}. Detects the encoding from the input stream.
108+
* Parses the current JSON type from a {@link InputStream}. Detects the encoding
109+
* from the input stream.
108110
*/
109111
public T from(InputStream stm) throws JsonParserException {
110112
return new JsonParser(new JsonTokener(stm), lazyNumbers, lazyStrings).parse(clazz);
@@ -119,7 +121,7 @@ public T from(InputStream stm) throws JsonParserException {
119121

120122
/**
121123
* Parses a {@link JsonObject} from a source.
122-
*
124+
*
123125
* <pre>
124126
* JsonObject json = {@link JsonParser}.object().from("{\"a\":[true,false], \"b\":1}");
125127
* </pre>
@@ -130,7 +132,7 @@ public static JsonParserContext<JsonObject> object() {
130132

131133
/**
132134
* Parses a {@link JsonArray} from a source.
133-
*
135+
*
134136
* <pre>
135137
* JsonArray json = {@link JsonParser}.array().from("[1, {\"a\":[true,false], \"b\":1}]");
136138
* </pre>
@@ -140,9 +142,11 @@ public static JsonParserContext<JsonArray> array() {
140142
}
141143

142144
/**
143-
* Parses any object from a source. For any valid JSON, returns either a null (for the JSON string 'null'), a
144-
* {@link String}, a {@link Number}, a {@link Boolean}, a {@link JsonObject} or a {@link JsonArray}.
145-
*
145+
* Parses any object from a source. For any valid JSON, returns either a null
146+
* (for the JSON string 'null'), a
147+
* {@link String}, a {@link Number}, a {@link Boolean}, a {@link JsonObject} or
148+
* a {@link JsonArray}.
149+
*
146150
* <pre>
147151
* Object json = {@link JsonParser}.any().from("{\"a\":[true,false], \"b\":1}");
148152
* Number json = ({@link Number}){@link JsonParser}.any().from("123.456e7");
@@ -156,94 +160,105 @@ public static JsonParserContext<Object> any() {
156160
* Parse a single JSON value from the string, expecting an EOF at the end.
157161
*/
158162
<T> T parse(Class<T> clazz) throws JsonParserException {
159-
advanceToken();
160-
Object parsed = currentValue();
161-
if (advanceToken() != JsonTokener.TOKEN_EOF)
162-
throw tokener.createParseException(null, "Expected end of input, got " + token, true);
163-
if (clazz != Object.class && (parsed == null || !clazz.isAssignableFrom(parsed.getClass())))
164-
throw tokener.createParseException(null,
165-
"JSON did not contain the correct type, expected " + clazz.getSimpleName() + ".",
166-
true);
167-
return clazz.cast(parsed);
163+
try {
164+
advanceToken();
165+
Object parsed = currentValue();
166+
if (advanceToken() != JsonTokener.TOKEN_EOF)
167+
throw tokener.createParseException(null, "Expected end of input, got " + token, true);
168+
if (clazz != Object.class && (parsed == null || !clazz.isAssignableFrom(parsed.getClass())))
169+
throw tokener.createParseException(null,
170+
"JSON did not contain the correct type, expected " + clazz.getSimpleName() + ".",
171+
true);
172+
return clazz.cast(parsed);
173+
} finally {
174+
// Automatically close the tokener to release resources back to the pool
175+
try {
176+
tokener.close();
177+
} catch (IOException e) {
178+
// Log or ignore IOException during cleanup - don't mask the original exception
179+
}
180+
}
168181
}
169182

170183
/**
171184
* Starts parsing a JSON value at the current token position.
172185
*/
173186
private Object currentValue() throws JsonParserException {
174-
// Only a value start token should appear when we're in the context of parsing a JSON value
187+
// Only a value start token should appear when we're in the context of parsing a
188+
// JSON value
175189
if (token >= JsonTokener.TOKEN_VALUE_MIN)
176190
return value;
177191
throw tokener.createParseException(null, "Expected JSON value, got " + token, true);
178192
}
179193

180194
/**
181-
* Consumes a token, first eating up any whitespace ahead of it. Note that number tokens are not necessarily valid
195+
* Consumes a token, first eating up any whitespace ahead of it. Note that
196+
* number tokens are not necessarily valid
182197
* numbers.
183198
*/
184199
private int advanceToken() throws JsonParserException {
185200
token = tokener.advanceToToken();
186201
switch (token) {
187-
case JsonTokener.TOKEN_ARRAY_START: // Inlined function to avoid additional stack
188-
JsonArray list = new JsonArray();
189-
if (advanceToken() != JsonTokener.TOKEN_ARRAY_END)
190-
while (true) {
191-
list.add(currentValue());
192-
if (advanceToken() == JsonTokener.TOKEN_ARRAY_END)
193-
break;
194-
if (token != JsonTokener.TOKEN_COMMA)
195-
throw tokener.createParseException(null,
196-
"Expected a comma or end of the array instead of " + token, true);
197-
if (advanceToken() == JsonTokener.TOKEN_ARRAY_END)
198-
throw tokener.createParseException(null, "Trailing comma found in array", true);
199-
}
200-
value = list;
201-
return token = JsonTokener.TOKEN_ARRAY_START;
202-
case JsonTokener.TOKEN_OBJECT_START: // Inlined function to avoid additional stack
203-
JsonObject map = new JsonObject();
204-
if (advanceToken() != JsonTokener.TOKEN_OBJECT_END)
205-
while (true) {
206-
if (token != JsonTokener.TOKEN_STRING)
207-
throw tokener.createParseException(null, "Expected STRING, got " + token, true);
208-
String key = lazyStrings ? value.toString() : (String) value;
209-
if (advanceToken() != JsonTokener.TOKEN_COLON)
210-
throw tokener.createParseException(null, "Expected COLON, got " + token, true);
211-
advanceToken();
212-
map.put(key, currentValue());
213-
if (advanceToken() == JsonTokener.TOKEN_OBJECT_END)
214-
break;
215-
if (token != JsonTokener.TOKEN_COMMA)
216-
throw tokener.createParseException(null,
217-
"Expected a comma or end of the object instead of " + token, true);
218-
if (advanceToken() == JsonTokener.TOKEN_OBJECT_END)
219-
throw tokener.createParseException(null, "Trailing object found in array", true);
220-
}
221-
value = map;
222-
return token = JsonTokener.TOKEN_OBJECT_START;
223-
case JsonTokener.TOKEN_TRUE:
224-
value = Boolean.TRUE;
225-
break;
226-
case JsonTokener.TOKEN_FALSE:
227-
value = Boolean.FALSE;
228-
break;
229-
case JsonTokener.TOKEN_NULL:
230-
value = null;
231-
break;
232-
case JsonTokener.TOKEN_STRING:
233-
char[] chars = tokener.reusableBuffer.array();
234-
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
235-
value = lazyStrings ? new LazyString(chars) : new String(chars);
236-
break;
237-
case JsonTokener.TOKEN_NUMBER:
238-
if (lazyNumbers) {
239-
chars = tokener.reusableBuffer.array();
202+
case JsonTokener.TOKEN_ARRAY_START: // Inlined function to avoid additional stack
203+
JsonArray list = new JsonArray();
204+
if (advanceToken() != JsonTokener.TOKEN_ARRAY_END)
205+
while (true) {
206+
list.add(currentValue());
207+
if (advanceToken() == JsonTokener.TOKEN_ARRAY_END)
208+
break;
209+
if (token != JsonTokener.TOKEN_COMMA)
210+
throw tokener.createParseException(null,
211+
"Expected a comma or end of the array instead of " + token, true);
212+
if (advanceToken() == JsonTokener.TOKEN_ARRAY_END)
213+
throw tokener.createParseException(null, "Trailing comma found in array", true);
214+
}
215+
value = list;
216+
return token = JsonTokener.TOKEN_ARRAY_START;
217+
case JsonTokener.TOKEN_OBJECT_START: // Inlined function to avoid additional stack
218+
JsonObject map = new JsonObject();
219+
if (advanceToken() != JsonTokener.TOKEN_OBJECT_END)
220+
while (true) {
221+
if (token != JsonTokener.TOKEN_STRING)
222+
throw tokener.createParseException(null, "Expected STRING, got " + token, true);
223+
String key = lazyStrings ? value.toString() : (String) value;
224+
if (advanceToken() != JsonTokener.TOKEN_COLON)
225+
throw tokener.createParseException(null, "Expected COLON, got " + token, true);
226+
advanceToken();
227+
map.put(key, currentValue());
228+
if (advanceToken() == JsonTokener.TOKEN_OBJECT_END)
229+
break;
230+
if (token != JsonTokener.TOKEN_COMMA)
231+
throw tokener.createParseException(null,
232+
"Expected a comma or end of the object instead of " + token, true);
233+
if (advanceToken() == JsonTokener.TOKEN_OBJECT_END)
234+
throw tokener.createParseException(null, "Trailing object found in array", true);
235+
}
236+
value = map;
237+
return token = JsonTokener.TOKEN_OBJECT_START;
238+
case JsonTokener.TOKEN_TRUE:
239+
value = Boolean.TRUE;
240+
break;
241+
case JsonTokener.TOKEN_FALSE:
242+
value = Boolean.FALSE;
243+
break;
244+
case JsonTokener.TOKEN_NULL:
245+
value = null;
246+
break;
247+
case JsonTokener.TOKEN_STRING:
248+
char[] chars = tokener.reusableBuffer.array();
240249
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
241-
value = new JsonLazyNumber(chars, tokener.isDouble);
242-
} else {
243-
value = parseNumber();
244-
}
245-
break;
246-
default:
250+
value = lazyStrings ? new LazyString(chars) : new String(chars);
251+
break;
252+
case JsonTokener.TOKEN_NUMBER:
253+
if (lazyNumbers) {
254+
chars = tokener.reusableBuffer.array();
255+
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
256+
value = new JsonLazyNumber(chars, tokener.isDouble);
257+
} else {
258+
value = parseNumber();
259+
}
260+
break;
261+
default:
247262
}
248263

249264
return token;

0 commit comments

Comments
 (0)