From d16ce1234998e5dc29057267b843cd6d57d37fd7 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 21 May 2020 00:35:31 +0200 Subject: [PATCH] Fixes #4892 - Non-blocking JSON parser. Updates after review. Signed-off-by: Simone Bordet --- .../eclipse/jetty/util/ajax/AsyncJSON.java | 40 +++++++++++++------ .../jetty/util/ajax/AsyncJSONTest.java | 40 +++++++++---------- 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java index 83087aa38db..be701f3ca6a 100644 --- a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java +++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java @@ -47,9 +47,9 @@ import org.eclipse.jetty.util.ajax.JSON.Convertor; * * // Tell the parser that the JSON string content * // is terminated and get the JSON object back. - * Map<String, Object> object = parser.eof(); + * Map<String, Object> object = parser.complete(); * - *

After the call to {@link #eof()} the parser can be reused to parse + *

After the call to {@link #complete()} the parser can be reused to parse * another JSON string.

*

Custom objects can be created by specifying a {@code "class"} or * {@code "x-class"} field:

@@ -64,7 +64,7 @@ import org.eclipse.jetty.util.ajax.JSON.Convertor; * """ * * parser.parse(json); - * com.acme.Person person = parser.eof(); + * com.acme.Person person = parser.complete(); * *

Class {@code com.acme.Person} must either implement {@link Convertible}, * or be mapped with a {@link Convertor} via {@link Factory#putConvertor(String, Convertor)}.

@@ -119,7 +119,7 @@ public class AsyncJSON * @param buffer the buffer to lookup the string from * @return a cached string or {@code null} */ - public String cached(ByteBuffer buffer) + private String cached(ByteBuffer buffer) { if (cache != null) { @@ -270,7 +270,7 @@ public class AsyncJSON { int position = buffer.position(); byte peek = buffer.get(position); - if (Character.isWhitespace((char)peek)) + if (isWhitespace(peek)) buffer.position(position + 1); else throw newInvalidJSON(buffer, "invalid character after JSON data"); @@ -374,7 +374,7 @@ public class AsyncJSON * @throws IllegalArgumentException if the JSON is malformed * @throws IllegalStateException if the no JSON was passed to the {@code parse()} methods */ - public R eof() + public R complete() { try { @@ -397,7 +397,7 @@ public class AsyncJSON { if (stack.peek().value == UNSET) throw new IllegalStateException("invalid state " + state); - return (R)complete(); + return (R)end(); } default: { @@ -439,7 +439,7 @@ public class AsyncJSON return new ArrayList<>(); } - private Object complete() + private Object end() { Object result = stack.peek().value; reset(); @@ -498,7 +498,7 @@ public class AsyncJSON return true; break; default: - if (Character.isWhitespace(peek)) + if (isWhitespace(peek)) { buffer.get(); break; @@ -854,7 +854,7 @@ public class AsyncJSON } default: { - if (Character.isWhitespace(peek)) + if (isWhitespace(peek)) { buffer.get(); break; @@ -904,7 +904,7 @@ public class AsyncJSON } default: { - if (Character.isWhitespace(currentByte)) + if (isWhitespace(currentByte)) { break; } @@ -947,7 +947,7 @@ public class AsyncJSON } default: { - if (Character.isWhitespace(peek)) + if (isWhitespace(peek)) { buffer.get(); break; @@ -1002,7 +1002,7 @@ public class AsyncJSON } default: { - if (Character.isWhitespace(peek)) + if (isWhitespace(peek)) { buffer.get(); break; @@ -1115,6 +1115,20 @@ public class AsyncJSON return new IllegalArgumentException(builder.toString()); } + private static boolean isWhitespace(byte ws) + { + switch (ws) + { + case ' ': + case '\n': + case '\r': + case '\t': + return true; + default: + return false; + } + } + /** *

The state of JSON parsing.

*/ diff --git a/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/AsyncJSONTest.java b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/AsyncJSONTest.java index d808ea400c4..fa93463b1f5 100644 --- a/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/AsyncJSONTest.java +++ b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/AsyncJSONTest.java @@ -78,7 +78,7 @@ public class AsyncJSONTest // Parse the whole input. assertTrue(parser.parse(bytes)); - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertTrue(parser.isEmpty()); // Parse byte by byte. @@ -90,7 +90,7 @@ public class AsyncJSONTest else assertFalse(parser.parse(new byte[]{b})); } - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertTrue(parser.isEmpty()); } @@ -140,12 +140,12 @@ public class AsyncJSONTest // Parse the whole input. assertTrue(parser.parse(bytes)); - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertTrue(parser.isEmpty()); ByteBuffer buffer = ByteBuffer.wrap(bytes); assertTrue(parser.parse(buffer)); assertFalse(buffer.hasRemaining()); - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertTrue(parser.isEmpty()); // Parse byte by byte. @@ -153,7 +153,7 @@ public class AsyncJSONTest { parser.parse(new byte[]{b}); } - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertTrue(parser.isEmpty()); } @@ -195,7 +195,7 @@ public class AsyncJSONTest assertThrows(IllegalArgumentException.class, () -> { parser.parse(bytes); - parser.eof(); + parser.complete(); }); assertTrue(parser.isEmpty()); @@ -206,7 +206,7 @@ public class AsyncJSONTest { parser.parse(new byte[]{b}); } - parser.eof(); + parser.complete(); }); assertTrue(parser.isEmpty()); } @@ -220,7 +220,7 @@ public class AsyncJSONTest // Parse the whole input. assertTrue(parser.parse(bytes)); - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertTrue(parser.isEmpty()); // Parse byte by byte. @@ -236,7 +236,7 @@ public class AsyncJSONTest assertFalse(parser.parse(new byte[]{b})); } } - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertTrue(parser.isEmpty()); } @@ -278,7 +278,7 @@ public class AsyncJSONTest assertThrows(IllegalArgumentException.class, () -> { parser.parse(bytes); - parser.eof(); + parser.complete(); }); assertTrue(parser.isEmpty()); @@ -289,7 +289,7 @@ public class AsyncJSONTest { parser.parse(new byte[]{b}); } - parser.eof(); + parser.complete(); }); assertTrue(parser.isEmpty()); } @@ -303,11 +303,11 @@ public class AsyncJSONTest // Parse the whole input. parser.parse(bytes); - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertTrue(parser.isEmpty()); ByteBuffer buffer = ByteBuffer.wrap(bytes); parser.parse(buffer); - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertFalse(buffer.hasRemaining()); assertTrue(parser.isEmpty()); @@ -316,7 +316,7 @@ public class AsyncJSONTest { parser.parse(new byte[]{b}); } - assertEquals(expected, parser.eof()); + assertEquals(expected, parser.complete()); assertTrue(parser.isEmpty()); } @@ -349,7 +349,7 @@ public class AsyncJSONTest assertThrows(IllegalArgumentException.class, () -> { parser.parse(bytes); - parser.eof(); + parser.complete(); }); assertTrue(parser.isEmpty()); @@ -360,7 +360,7 @@ public class AsyncJSONTest { parser.parse(new byte[]{b}); } - parser.eof(); + parser.complete(); }); assertTrue(parser.isEmpty()); } @@ -379,7 +379,7 @@ public class AsyncJSONTest AsyncJSON parser = factory.newAsyncJSON(); assertTrue(parser.parse(UTF_8.encode(json))); - Map result = parser.eof(); + Map result = parser.complete(); Object value1 = result.get("f1"); assertTrue(value1 instanceof CustomConvertible); @@ -389,7 +389,7 @@ public class AsyncJSONTest assertSame(convertor, factory.removeConvertor(CustomConvertor.class.getName())); assertTrue(parser.parse(UTF_8.encode(json))); - result = parser.eof(); + result = parser.complete(); value1 = result.get("f1"); assertTrue(value1 instanceof CustomConvertible); @@ -467,7 +467,7 @@ public class AsyncJSONTest "}]"; assertTrue(parser.parse(UTF_8.encode(json))); - List messages = parser.eof(); + List messages = parser.complete(); for (CustomMap message : messages) { @@ -491,7 +491,7 @@ public class AsyncJSONTest String json = "{\"foo\": [\"foo\", \"foo\"]}"; parser.parse(UTF_8.encode(json)); - Map object = parser.eof(); + Map object = parser.complete(); Map.Entry entry = object.entrySet().iterator().next(); assertSame(foo, entry.getKey());