Convert JsonValueSource to Kotlin (#1489)

* Rename .java to .kt

* Convert JsonValueSource to Kotlin
This commit is contained in:
Zac Sweers
2022-01-10 12:23:09 -05:00
committed by GitHub
parent d7b70637d2
commit 156e9007fd
2 changed files with 208 additions and 235 deletions

View File

@@ -1,235 +0,0 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.moshi;
import java.io.EOFException;
import java.io.IOException;
import okio.Buffer;
import okio.BufferedSource;
import okio.ByteString;
import okio.Source;
import okio.Timeout;
/**
* This source reads a prefix of another source as a JSON value and then terminates. It can read
* top-level arrays, objects, or strings only.
*
* <p>It implements {@linkplain JsonReader#setLenient lenient parsing} and has no mechanism to
* enforce strict parsing. If the input is not valid or lenient JSON the behavior of this source is
* unspecified.
*/
final class JsonValueSource implements Source {
static final ByteString STATE_JSON = ByteString.encodeUtf8("[]{}\"'/#");
static final ByteString STATE_SINGLE_QUOTED = ByteString.encodeUtf8("'\\");
static final ByteString STATE_DOUBLE_QUOTED = ByteString.encodeUtf8("\"\\");
static final ByteString STATE_END_OF_LINE_COMMENT = ByteString.encodeUtf8("\r\n");
static final ByteString STATE_C_STYLE_COMMENT = ByteString.encodeUtf8("*");
static final ByteString STATE_END_OF_JSON = ByteString.EMPTY;
private final BufferedSource source;
private final Buffer buffer;
/** If non-empty, data from this should be returned before data from {@link #source}. */
private final Buffer prefix;
/**
* The state indicates what kind of data is readable at {@link #limit}. This also serves
* double-duty as the type of bytes we're interested in while in this state.
*/
private ByteString state;
/**
* The level of nesting of arrays and objects. When the end of string, array, or object is
* reached, this should be compared against 0. If it is zero, then we've read a complete value and
* this source is exhausted.
*/
private int stackSize;
/** The number of bytes immediately returnable to the caller. */
private long limit = 0;
private boolean closed = false;
JsonValueSource(BufferedSource source) {
this(source, new Buffer(), STATE_JSON, 0);
}
JsonValueSource(BufferedSource source, Buffer prefix, ByteString state, int stackSize) {
this.source = source;
this.buffer = source.getBuffer();
this.prefix = prefix;
this.state = state;
this.stackSize = stackSize;
}
/**
* Advance {@link #limit} until any of these conditions are met:
*
* <ul>
* <li>Limit is at least {@code byteCount}. We can satisfy the caller's request!
* <li>The JSON value is complete. This stream is exhausted.
* <li>We have some data to return and returning more would require reloading the buffer. We
* prefer to return some data immediately when more data requires blocking.
* </ul>
*
* @throws EOFException if the stream is exhausted before the JSON object completes.
*/
private void advanceLimit(long byteCount) throws IOException {
while (limit < byteCount) {
// If we've finished the JSON object, we're done.
if (state == STATE_END_OF_JSON) {
return;
}
// If we can't return any bytes without more data in the buffer, grow the buffer.
if (limit == buffer.size()) {
if (limit > 0L) return;
source.require(1L);
}
// Find the next interesting character for the current state. If the buffer doesn't have one,
// then we can read the entire buffer.
long index = buffer.indexOfElement(state, limit);
if (index == -1L) {
limit = buffer.size();
continue;
}
byte b = buffer.getByte(index);
if (state == STATE_JSON) {
switch (b) {
case '[':
case '{':
stackSize++;
limit = index + 1;
break;
case ']':
case '}':
stackSize--;
if (stackSize == 0) state = STATE_END_OF_JSON;
limit = index + 1;
break;
case '\"':
state = STATE_DOUBLE_QUOTED;
limit = index + 1;
break;
case '\'':
state = STATE_SINGLE_QUOTED;
limit = index + 1;
break;
case '/':
source.require(index + 2);
byte b2 = buffer.getByte(index + 1);
if (b2 == '/') {
state = STATE_END_OF_LINE_COMMENT;
limit = index + 2;
} else if (b2 == '*') {
state = STATE_C_STYLE_COMMENT;
limit = index + 2;
} else {
limit = index + 1;
}
break;
case '#':
state = STATE_END_OF_LINE_COMMENT;
limit = index + 1;
break;
}
} else if (state == STATE_SINGLE_QUOTED || state == STATE_DOUBLE_QUOTED) {
if (b == '\\') {
source.require(index + 2);
limit = index + 2;
} else {
state = (stackSize > 0) ? STATE_JSON : STATE_END_OF_JSON;
limit = index + 1;
}
} else if (state == STATE_C_STYLE_COMMENT) {
source.require(index + 2);
if (buffer.getByte(index + 1) == '/') {
limit = index + 2;
state = STATE_JSON;
} else {
limit = index + 1;
}
} else if (state == STATE_END_OF_LINE_COMMENT) {
limit = index + 1;
state = STATE_JSON;
} else {
throw new AssertionError();
}
}
}
/**
* Discards any remaining JSON data in this source that was left behind after it was closed. It is
* an error to call {@link #read} after calling this method.
*/
public void discard() throws IOException {
closed = true;
while (state != STATE_END_OF_JSON) {
advanceLimit(8192);
source.skip(limit);
}
}
@Override
public long read(Buffer sink, long byteCount) throws IOException {
if (closed) throw new IllegalStateException("closed");
if (byteCount == 0) return 0L;
// If this stream has a prefix, consume that first.
if (!prefix.exhausted()) {
long prefixResult = prefix.read(sink, byteCount);
byteCount -= prefixResult;
if (buffer.exhausted()) return prefixResult; // Defer a blocking call.
long suffixResult = read(sink, byteCount);
return suffixResult != -1L ? suffixResult + prefixResult : prefixResult;
}
advanceLimit(byteCount);
if (limit == 0) {
if (state != STATE_END_OF_JSON) throw new AssertionError();
return -1L;
}
long result = Math.min(byteCount, limit);
sink.write(buffer, result);
limit -= result;
return result;
}
@Override
public Timeout timeout() {
return source.timeout();
}
@Override
public void close() throws IOException {
// Note that this does not close the underlying source; that's the creator's responsibility.
closed = true;
}
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.moshi
import okio.Buffer
import okio.BufferedSource
import okio.ByteString
import okio.ByteString.Companion.EMPTY
import okio.ByteString.Companion.encodeUtf8
import okio.EOFException
import okio.Source
import kotlin.jvm.JvmOverloads
import kotlin.math.min
/**
* This source reads a prefix of another source as a JSON value and then terminates. It can read
* top-level arrays, objects, or strings only.
*
* It implements [lenient parsing][JsonReader.setLenient] and has no mechanism to
* enforce strict parsing. If the input is not valid or lenient JSON the behavior of this source is
* unspecified.
*/
internal class JsonValueSource @JvmOverloads constructor(
private val source: BufferedSource,
/** If non-empty, data from this should be returned before data from [source]. */
private val prefix: Buffer = Buffer(),
/**
* The state indicates what kind of data is readable at [limit]. This also serves
* double-duty as the type of bytes we're interested in while in this state.
*/
private var state: ByteString = STATE_JSON,
/**
* The level of nesting of arrays and objects. When the end of string, array, or object is
* reached, this should be compared against 0. If it is zero, then we've read a complete value and
* this source is exhausted.
*/
private var stackSize: Int = 0
) : Source {
private val buffer: Buffer = source.buffer
/** The number of bytes immediately returnable to the caller. */
private var limit: Long = 0
private var closed = false
/**
* Advance [limit] until any of these conditions are met:
* * Limit is at least `byteCount`. We can satisfy the caller's request!
* * The JSON value is complete. This stream is exhausted.
* * We have some data to return and returning more would require reloading the buffer. We prefer to return some
* data immediately when more data requires blocking.
*
* @throws EOFException if the stream is exhausted before the JSON object completes.
*/
private fun advanceLimit(byteCount: Long) {
while (limit < byteCount) {
// If we've finished the JSON object, we're done.
if (state === STATE_END_OF_JSON) {
return
}
// If we can't return any bytes without more data in the buffer, grow the buffer.
if (limit == buffer.size) {
if (limit > 0L) return
source.require(1L)
}
// Find the next interesting character for the current state. If the buffer doesn't have one,
// then we can read the entire buffer.
val index = buffer.indexOfElement(state, limit)
if (index == -1L) {
limit = buffer.size
continue
}
val b = buffer[index]
when {
state === STATE_JSON -> when (b.toInt().toChar()) {
'[', '{' -> {
stackSize++
limit = index + 1
}
']', '}' -> {
stackSize--
if (stackSize == 0) state = STATE_END_OF_JSON
limit = index + 1
}
'\"' -> {
state = STATE_DOUBLE_QUOTED
limit = index + 1
}
'\'' -> {
state = STATE_SINGLE_QUOTED
limit = index + 1
}
'/' -> {
source.require(index + 2)
when (buffer[index + 1]) {
'/'.code.toByte() -> {
state = STATE_END_OF_LINE_COMMENT
limit = index + 2
}
'*'.code.toByte() -> {
state = STATE_C_STYLE_COMMENT
limit = index + 2
}
else -> {
limit = index + 1
}
}
}
'#' -> {
state = STATE_END_OF_LINE_COMMENT
limit = index + 1
}
}
state === STATE_SINGLE_QUOTED || state === STATE_DOUBLE_QUOTED -> {
if (b == '\\'.code.toByte()) {
source.require(index + 2)
limit = index + 2
} else {
state = if (stackSize > 0) STATE_JSON else STATE_END_OF_JSON
limit = index + 1
}
}
state === STATE_C_STYLE_COMMENT -> {
source.require(index + 2)
if (buffer[index + 1] == '/'.code.toByte()) {
limit = index + 2
state = STATE_JSON
} else {
limit = index + 1
}
}
state === STATE_END_OF_LINE_COMMENT -> {
limit = index + 1
state = STATE_JSON
}
else -> {
throw AssertionError()
}
}
}
}
/**
* Discards any remaining JSON data in this source that was left behind after it was closed. It is
* an error to call [read] after calling this method.
*/
fun discard() {
closed = true
while (state !== STATE_END_OF_JSON) {
advanceLimit(8192)
source.skip(limit)
}
}
override fun read(sink: Buffer, byteCount: Long): Long {
var mutableByteCount = byteCount
check(!closed) { "closed" }
if (mutableByteCount == 0L) return 0L
// If this stream has a prefix, consume that first.
if (!prefix.exhausted()) {
val prefixResult = prefix.read(sink, mutableByteCount)
mutableByteCount -= prefixResult
if (buffer.exhausted()) return prefixResult // Defer a blocking call.
val suffixResult = read(sink, mutableByteCount)
return if (suffixResult != -1L) suffixResult + prefixResult else prefixResult
}
advanceLimit(mutableByteCount)
if (limit == 0L) {
if (state !== STATE_END_OF_JSON) throw AssertionError()
return -1L
}
val result = min(mutableByteCount, limit)
sink.write(buffer, result)
limit -= result
return result
}
override fun timeout() = source.timeout()
override fun close() {
// Note that this does not close the underlying source; that's the creator's responsibility.
closed = true
}
companion object {
val STATE_JSON: ByteString = "[]{}\"'/#".encodeUtf8()
val STATE_SINGLE_QUOTED: ByteString = "'\\".encodeUtf8()
val STATE_DOUBLE_QUOTED: ByteString = "\"\\".encodeUtf8()
val STATE_END_OF_LINE_COMMENT: ByteString = "\r\n".encodeUtf8()
val STATE_C_STYLE_COMMENT: ByteString = "*".encodeUtf8()
val STATE_END_OF_JSON: ByteString = EMPTY
}
}