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