Fixes #4892 - Non-blocking JSON parser.
Updates after review. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
e588a1bd9d
commit
d16ce12349
|
@ -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<String, Object> object = parser.eof();
|
* Map<String, Object> 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>
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in New Issue