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
* // 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>
* <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>
* <p>Custom objects can be created by specifying a {@code "class"} or
* {@code "x-class"} field:</p>
@ -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();
* </pre>
* <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>
@ -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> R eof()
public <R> 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;
}
}
/**
* <p>The state of JSON parsing.</p>
*/

View File

@ -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<String, Object> result = parser.eof();
Map<String, Object> 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<CustomMap> messages = parser.eof();
List<CustomMap> 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<String, Object> object = parser.eof();
Map<String, Object> object = parser.complete();
Map.Entry<String, Object> entry = object.entrySet().iterator().next();
assertSame(foo, entry.getKey());