io.r2dbc.postgresql.codec.Json Maven / Gradle / Ivy
/*
* Copyright 2019 the original author or authors.
*
* 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 io.r2dbc.postgresql.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.r2dbc.postgresql.util.Assert;
import reactor.util.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
/**
* Value object to represent JSON values.
* JSON values are divided into input and output values.
* Input values are provided by code that wants to bind values to a query. Input values are intended to be consumed by the driver.
* Input values should not be consumed by application code.
*
*
Output values are returned by the driver to be consumed by application code using the following methods:
*
* - {@link #mapBuffer(Function)}
* - {@link #mapByteBuf(Function)}
* - {@link #mapInputStream(Function)}
* - {@link #asString()}
* - {@link #asArray()}
*
*
* JSON values should be generally considered for single-consumption only. Output values retain a reference to a potentially pooled memory buffer and must be consumed to avoid memory leaks.
*/
public abstract class Json {
Json() {
}
/**
* Create a {@link Json} object from a {@link ByteBuffer}.
*
* @param buffer the JSON value as {@link ByteBuffer}
* @return {@link Json} object from a {@link ByteBuffer}.
* @throws IllegalArgumentException if {@code buffer} is {@code null}
*/
public static Json of(ByteBuffer buffer) {
return new JsonByteBufInput(Unpooled.wrappedBuffer(Assert.requireNonNull(buffer, "buffer must not be null")));
}
/**
* Create a {@link Json} object from a {@link ByteBuf}.
*
The {@link ByteBuf} is released after encoding the value.
*
* @param buffer the JSON value as {@link ByteBuf}
* @return {@link Json} object from a {@link ByteBuf}.
* @throws IllegalArgumentException if {@code buffer} is {@code null}
*/
public static Json of(ByteBuf buffer) {
return new JsonByteBufInput(Assert.requireNonNull(buffer, "buffer must not be null"));
}
/**
* Create a {@link Json} object from a {@link InputStream}.
*
The {@link InputStream} is {@link InputStream#close() closed} after encoding the value.
*
* @param inputStream the JSON value as {@link InputStream}
* @return {@link Json} object from a {@link InputStream}.
* @throws IllegalArgumentException if {@code inputStream} is {@code null}
*/
public static Json of(InputStream inputStream) {
return new JsonInputStreamInput(Assert.requireNonNull(inputStream, "inputStream must not be null"));
}
/**
* Create a {@link Json} object from a {@code byte[] value}.
*
* @param value the JSON value as {@code byte[]}
* @return {@link Json} object from a {@code byte[] value}.
* @throws IllegalArgumentException if {@code value} is {@code null}
*/
public static Json of(byte[] value) {
return new JsonByteArrayInput(Assert.requireNonNull(value, "buffer must not be null"));
}
/**
* Create a {@link Json} object from a {@link String}. Uses UTF-8 encoding to convert the value into its binary representation.
*
* @param value the JSON value as {@link String}
* @return {@link Json} object from a {@link String}.
* @throws IllegalArgumentException if {@code value} is {@code null}
*/
public static Json of(String value) {
return new JsonByteArrayInput(Assert.requireNonNull(value, "value must not be null").getBytes(StandardCharsets.UTF_8));
}
/**
* Returns an object consisting of the result of applying the given
* mapping {@link Function} to the {@link ByteBuffer} of this JSON value.
*
Consumption methods should be called exactly once as the underlying JSON value is typically released after consumption.
*
* @param mappingFunction mapping function that gets applied to the {@link ByteBuffer} representation of this JSON value.
* @param return type.
* @return the mapped value. Can be {@code null}.
*/
@Nullable
public abstract T mapBuffer(Function mappingFunction);
/**
* Returns an object consisting of the result of applying the given
* mapping {@link Function} to the {@link ByteBuf} of this JSON value.
* Consumption methods should be called exactly once as the underlying JSON value is typically released after consumption.
*
* @param mappingFunction mapping function that gets applied to the {@link ByteBuf} representation of this JSON value.
* @param return type.
* @return the mapped value. Can be {@code null}.
*/
@Nullable
public abstract T mapByteBuf(Function mappingFunction);
/**
* Returns an object consisting of the result of applying the given
* mapping {@link Function} to the {@link InputStream} of this JSON value.
* Consumption methods should be called exactly once as the underlying JSON value is typically released after consumption.
*
* @param mappingFunction mapping function that gets applied to the {@link InputStream} representation of this JSON value.
* @param return type.
* @return the mapped value. Can be {@code null}.
*/
@Nullable
public abstract T mapInputStream(Function mappingFunction);
/**
* Returns the value as {@code byte[]}.
* Consumption methods should be called exactly once as the underlying JSON value is typically released after consumption.
*
* @return the contents of the JSON value as {@code byte[]}.
*/
public abstract byte[] asArray();
/**
* Returns the value as {@link String}.
*
Consumption methods should be called exactly once as the underlying JSON value is typically released after consumption.
*
* @return the contents of the JSON value as {@link String}.
*/
public abstract String asString();
/**
* JSON input value.
*/
static abstract class JsonInput extends Json {
final T value;
JsonInput(T value) {
this.value = value;
}
}
/**
* JSON input as {@link ByteBuf}.
*/
static final class JsonByteBufInput extends JsonInput {
JsonByteBufInput(ByteBuf value) {
super(value);
}
@Override
public T mapBuffer(Function mappingFunction) {
Assert.requireNonNull(mappingFunction, "mappingFunction must not be null");
return mappingFunction.apply(this.value.nioBuffer());
}
@Override
public T mapByteBuf(Function mappingFunction) {
Assert.requireNonNull(mappingFunction, "mappingFunction must not be null");
return mappingFunction.apply(this.value);
}
@Override
public T mapInputStream(Function mappingFunction) {
Assert.requireNonNull(mappingFunction, "mappingFunction must not be null");
return mappingFunction.apply(new ByteBufInputStream(this.value));
}
@Override
public byte[] asArray() {
return ByteBufUtil.getBytes(this.value);
}
@Override
public String asString() {
return this.value.toString(0, this.value.readableBytes(), StandardCharsets.UTF_8);
}
@Override
public String toString() {
return "JsonByteBufInput{" +
asString() +
'}';
}
}
/**
* JSON input as {@code byte[]}.
*/
static final class JsonByteArrayInput extends JsonInput {
JsonByteArrayInput(byte[] value) {
super(value);
}
@Override
public T mapBuffer(Function mappingFunction) {
Assert.requireNonNull(mappingFunction, "mappingFunction must not be null");
return mappingFunction.apply(ByteBuffer.wrap(asArray()));
}
@Override
public T mapByteBuf(Function mappingFunction) {
Assert.requireNonNull(mappingFunction, "mappingFunction must not be null");
return mappingFunction.apply(Unpooled.wrappedBuffer(asArray()));
}
@Override
public T mapInputStream(Function mappingFunction) {
Assert.requireNonNull(mappingFunction, "mappingFunction must not be null");
return mappingFunction.apply(new ByteArrayInputStream(asArray()));
}
@Override
public byte[] asArray() {
return this.value;
}
@Override
public String asString() {
return new String(this.value);
}
@Override
public String toString() {
return "JsonByteArrayInput{" +
asString() +
'}';
}
}
/**
* JSON input as {@link InputStream}.
*/
static final class JsonInputStreamInput extends JsonInput {
JsonInputStreamInput(InputStream value) {
super(value);
}
@Override
public T mapBuffer(Function mappingFunction) {
Assert.requireNonNull(mappingFunction, "mappingFunction must not be null");
return mappingFunction.apply(ByteBuffer.wrap(asArray()));
}
@Override
public T mapByteBuf(Function mappingFunction) {
Assert.requireNonNull(mappingFunction, "mappingFunction must not be null");
return mappingFunction.apply(Unpooled.wrappedBuffer(asArray()));
}
@Override
public T mapInputStream(Function mappingFunction) {
Assert.requireNonNull(mappingFunction, "mappingFunction must not be null");
return mappingFunction.apply(this.value);
}
@Override
public byte[] asArray() {
if (this.value.markSupported()) {
this.value.mark(Integer.MAX_VALUE);
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
try {
while ((bytesRead = this.value.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
if (this.value.markSupported()) {
this.value.reset();
}
} catch (IOException e) {
throw new IllegalStateException("Cannot read bytes from InputStream", e);
}
return out.toByteArray();
}
@Override
public String asString() {
return new String(asArray(), StandardCharsets.UTF_8);
}
@Override
public String toString() {
return "JsonInputStreamInput{" +
this.value +
'}';
}
}
/**
* JSON output value.
*/
static final class JsonOutput extends Json {
final ByteBuf buffer;
volatile boolean released;
JsonOutput(ByteBuf buffer) {
this.buffer = buffer;
}
@Override
public byte[] asArray() {
assertNotReleased();
try {
byte[] bytes = new byte[this.buffer.readableBytes()];
this.buffer.readBytes(bytes);
return bytes;
} finally {
release();
}
}
@Override
public String asString() {
return new String(asArray());
}
void assertNotReleased() {
if (this.released) {
throw new IllegalStateException("JSON is already released");
}
}
@Override
public T mapBuffer(Function mappingFunction) {
assertNotReleased();
ByteBuffer buffer = ByteBuffer.allocate(this.buffer.readableBytes());
this.buffer.readBytes(buffer);
buffer.flip();
release();
return mappingFunction.apply(buffer);
}
@Override
public T mapByteBuf(Function mappingFunction) {
assertNotReleased();
try {
return mappingFunction.apply(this.buffer);
} finally {
release();
}
}
@Override
public T mapInputStream(Function mappingFunction) {
assertNotReleased();
try {
return mappingFunction.apply(new ByteBufInputStream(this.buffer));
} finally {
release();
}
}
private void release() {
this.released = true;
this.buffer.release();
}
@Override
public String toString() {
if (this.released) {
return "JsonOutput{[released]}";
}
return "JsonOutput{" +
this.buffer.toString(StandardCharsets.UTF_8)
+ "}";
}
}
}