Fixes #4892 - Non-blocking JSON parser.

Updates after review.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2020-05-21 00:35:31 +02:00
parent e588a1bd9d
commit d16ce12349
2 changed files with 47 additions and 33 deletions

View File

@ -47,9 +47,9 @@ import org.eclipse.jetty.util.ajax.JSON.Convertor;
* *
* // Tell the parser that the JSON string content * // Tell the parser that the JSON string content
* // is terminated and get the JSON object back. * // is terminated and get the JSON object back.
* Map&lt;String, Object&gt; object = parser.eof(); * Map&lt;String, Object&gt; object = parser.complete();
* </pre> * </pre>
* <p>After the call to {@link #eof()} the parser can be reused to parse * <p>After the call to {@link #complete()} the parser can be reused to parse
* another JSON string.</p> * another JSON string.</p>
* <p>Custom objects can be created by specifying a {@code "class"} or * <p>Custom objects can be created by specifying a {@code "class"} or
* {@code "x-class"} field:</p> * {@code "x-class"} field:</p>
@ -64,7 +64,7 @@ import org.eclipse.jetty.util.ajax.JSON.Convertor;
* """ * """
* *
* parser.parse(json); * parser.parse(json);
* com.acme.Person person = parser.eof(); * com.acme.Person person = parser.complete();
* </pre> * </pre>
* <p>Class {@code com.acme.Person} must either implement {@link Convertible}, * <p>Class {@code com.acme.Person} must either implement {@link Convertible},
* or be mapped with a {@link Convertor} via {@link Factory#putConvertor(String, Convertor)}.</p> * or be mapped with a {@link Convertor} via {@link Factory#putConvertor(String, Convertor)}.</p>
@ -119,7 +119,7 @@ public class AsyncJSON
* @param buffer the buffer to lookup the string from * @param buffer the buffer to lookup the string from
* @return a cached string or {@code null} * @return a cached string or {@code null}
*/ */
public String cached(ByteBuffer buffer) private String cached(ByteBuffer buffer)
{ {
if (cache != null) if (cache != null)
{ {
@ -270,7 +270,7 @@ public class AsyncJSON
{ {
int position = buffer.position(); int position = buffer.position();
byte peek = buffer.get(position); byte peek = buffer.get(position);
if (Character.isWhitespace((char)peek)) if (isWhitespace(peek))
buffer.position(position + 1); buffer.position(position + 1);
else else
throw newInvalidJSON(buffer, "invalid character after JSON data"); throw newInvalidJSON(buffer, "invalid character after JSON data");
@ -374,7 +374,7 @@ public class AsyncJSON
* @throws IllegalArgumentException if the JSON is malformed * @throws IllegalArgumentException if the JSON is malformed
* @throws IllegalStateException if the no JSON was passed to the {@code parse()} methods * @throws IllegalStateException if the no JSON was passed to the {@code parse()} methods
*/ */
public <R> R eof() public <R> R complete()
{ {
try try
{ {
@ -397,7 +397,7 @@ public class AsyncJSON
{ {
if (stack.peek().value == UNSET) if (stack.peek().value == UNSET)
throw new IllegalStateException("invalid state " + state); throw new IllegalStateException("invalid state " + state);
return (R)complete(); return (R)end();
} }
default: default:
{ {
@ -439,7 +439,7 @@ public class AsyncJSON
return new ArrayList<>(); return new ArrayList<>();
} }
private Object complete() private Object end()
{ {
Object result = stack.peek().value; Object result = stack.peek().value;
reset(); reset();
@ -498,7 +498,7 @@ public class AsyncJSON
return true; return true;
break; break;
default: default:
if (Character.isWhitespace(peek)) if (isWhitespace(peek))
{ {
buffer.get(); buffer.get();
break; break;
@ -854,7 +854,7 @@ public class AsyncJSON
} }
default: default:
{ {
if (Character.isWhitespace(peek)) if (isWhitespace(peek))
{ {
buffer.get(); buffer.get();
break; break;
@ -904,7 +904,7 @@ public class AsyncJSON
} }
default: default:
{ {
if (Character.isWhitespace(currentByte)) if (isWhitespace(currentByte))
{ {
break; break;
} }
@ -947,7 +947,7 @@ public class AsyncJSON
} }
default: default:
{ {
if (Character.isWhitespace(peek)) if (isWhitespace(peek))
{ {
buffer.get(); buffer.get();
break; break;
@ -1002,7 +1002,7 @@ public class AsyncJSON
} }
default: default:
{ {
if (Character.isWhitespace(peek)) if (isWhitespace(peek))
{ {
buffer.get(); buffer.get();
break; break;
@ -1115,6 +1115,20 @@ public class AsyncJSON
return new IllegalArgumentException(builder.toString()); 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;
}
}
/** /**
* <p>The state of JSON parsing.</p> * <p>The state of JSON parsing.</p>
*/ */

View File

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