cn.zenliu.units.codec.Codec Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of units-codec Show documentation
Show all versions of units-codec Show documentation
Simple binary codec units base on netty-buffer
/*
* Source of units
* Copyright (C) 2023. Zen.Liu
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0"
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Class Path Exception
* Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License cover the whole combination.
* As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version.
*/
package cn.zenliu.units.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import lombok.Cleanup;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
/**
* Binary codec: encode or decode values from binary.
* There are no schema inside the binary, sender and receiver must already acknowledge the schema information .
*
*
* Basic format:
*
* - simple type: encode as little endian binary format without any header.
* - nullable type: encode one byte (0 for null , 1 for nonnull) as header.
* - repeated type: encode zigzag-var-int32 length (negative for null,0 for empty ) of elements as header.
* - tuple type: encode or decode values in tuple order.
* - {@link Serializer} type: encode or decode use Serializer method.
* - {@link Reference} type: only encode or decode the {@link Reference#identifier()}{@link Serializer}.
*
* For CodecBase implement have those out-of-box support:
*
* - primitives and array (integers shorter than 32bit are not directly supported except byte)
* - boxed type and array (integers shorter than 32bit are not directly supported except byte) )
* - Map List Set Collection Object-Array type; Those type can override by TypeInfoFactory
* - String, Instant, Timestamp , BigDecimal, BigInteger,Duration, Period; Those type can override by TypeInfoFactory
* - Pojo with default public empty constructor; Can override by TypeInfoFactory
* - Record with public constructor (must have components); Can override by TypeInfoFactory
*
*
*
* @author Zen.Liu
* @since 2023-08-22
*/
@SuppressWarnings("UnusedReturnValue")
public interface Codec {
/**
* call this before use this codec;
* This will initialize any {@link TypeInfoFactory} that inherited {@link TypeInfoFactory.Initialize}.
* This method may call multiple times with different value.
*/
void initialize(@Nullable Object value);
/**
* Encode value into buffer.
* Note this will write a zero byte if value is null.
*
* @param buf output buffer
* @param value encode value
* @return output buffer
*/
ByteBuf encode(ByteBuf buf, Object value);
default ByteBuf encode(ByteBuf buf, T value, Class type) {
return encode(buf, value, typeInfo(type));
}
default ByteBuf encode(ByteBuf buf, T value, TypeRef type) {
return encode(buf, value, typeInfo(type));
}
/**
* Encode value into buffer use specified TypeInfo.
*
* @param buf output buffer
* @param value encode value
* @param info specified TypeInfo
* @param type
* @return output buffer
*/
ByteBuf encode(ByteBuf buf, T value, TypeInfo super T> info);
/**
* Decode value from buffer
* Note: buffer should release after read ( decrease reference count by one ).
*
* @param buf input buffer
* @param type typeinfo
* @param type contains in buffer
* @return read value
*/
T decode(ByteBuf buf, TypeInfo type);
default T decode(ByteBuf buf, Class type) {
return decode(buf, typeInfo(type));
}
default T decode(ByteBuf buf, TypeRef type) {
return decode(buf, typeInfo(type));
}
/**
* Make a type-info from java type.
*
* @param type java type or {@link TypeRef}
* @param type present by the java type
* @return type info
*/
@Nullable TypeInfo typeInfo(Type type);
default Optional> resolve(Type type) {
return Optional.ofNullable(typeInfo(type));
}
default ByteBuf encode(ByteBuf buf, boolean value) {
return encodeBoolean(buf, value);
}
default ByteBuf encode(ByteBuf buf, int value) {
return encodeInt32(buf, value);
}
default ByteBuf encode(ByteBuf buf, long value) {
return encodeInt64(buf, value);
}
default ByteBuf encode(ByteBuf buf, double value) {
return encodeFloat64(buf, value);
}
default ByteBuf encode(ByteBuf buf, float value) {
return encodeFloat32(buf, value);
}
//region codec utilities
/**
* default encode for boolean
*/
static ByteBuf encodeBoolean(ByteBuf buf, boolean value) {
return buf.writeByte(value ? 1 : 0);
}
/**
* default decode for boolean
*/
static boolean decodeBoolean(ByteBuf buf) {
return buf.readByte() > 0;
}
/**
* default encode for int32 (use zig-zag-var-int codec)
*/
static ByteBuf encodeInt32(ByteBuf buf, int value) {
return zigZagVarInt32(buf, value);
}
/**
* default decode for int32 (use zig-zag-var-int codec)
*/
static int decodeInt32(ByteBuf buf) {
return zigZagVarInt32(buf);
}
/**
* default encode for int64 (use zig-zag-var-int codec)
*/
static ByteBuf encodeInt64(ByteBuf buf, long value) {
return zigZagVarInt64(buf, value);
}
/**
* default decode for int64 (use zig-zag-var-int codec)
*/
static long decodeInt64(ByteBuf buf) {
return zigZagVarInt64(buf);
}
/**
* default encode for float32
*/
static ByteBuf encodeFloat32(ByteBuf buf, float value) {
return buf.writeFloatLE(value);
}
/**
* default decode for float32
*/
static float decodeFloat32(ByteBuf buf) {
return buf.readFloatLE();
}
/**
* default encode for float64
*/
static ByteBuf encodeFloat64(ByteBuf buf, double value) {
return buf.writeDoubleLE(value);
}
/**
* default decode for float32
*/
static double decodeFloat64(ByteBuf buf) {
return buf.readDoubleLE();
}
/**
* default encode for bytes
*/
static ByteBuf encodeByteArray(ByteBuf buf, byte[] value) {
if (value == null) return encodeInt32(buf, -1);
encodeInt32(buf, value.length);
return buf.writeBytes(value);
}
/**
* default decode for bytes
*/
static byte[] decodeByteArray(ByteBuf buf) {
var n = decodeInt32(buf);
if (n < 0) return null;
if (n == 0) return new byte[0];
var bytes = new byte[n];
buf.readBytes(bytes);
return bytes;
}
static ByteBuf encodeString(ByteBuf buf, String value) {
if (value == null) return Codec.zigZagVarInt32(buf, -1);
else if (value.isEmpty()) return Codec.zigZagVarInt32(buf, 0);
else {
@Cleanup("release") var b = ByteBufAllocator.DEFAULT.buffer();
var n = b.writeCharSequence(value, StandardCharsets.UTF_8);
Codec.zigZagVarInt32(buf, n);
return buf.writeBytes(b);
}
}
static String decodeString(ByteBuf buf) {
var n = Codec.zigZagVarInt32(buf);
if (n < 0) return null;
if (n == 0) return "";
return buf.readSlice(n).toString(StandardCharsets.UTF_8);
}
static ByteBuf encodeAnyArray(ByteBuf buf, Object value, BiConsumer writer) {
if (value == null) return encodeInt32(buf, -1);
var n = Array.getLength(value);
encodeInt32(buf, n);
for (int i = 0; i < n; i++) {
writer.accept(buf, Array.get(value, i));
}
return buf;
}
/**
* default decode for Array
*/
@SuppressWarnings("rawtypes")
static Object decodeAnyArray(ByteBuf buf, Class elementType, Function reader) {
var n = decodeInt32(buf);
if (n < 0) return null;
if (n == 0) return Array.newInstance(elementType, 0);
var values = Array.newInstance(elementType, n);
for (int i = 0; i < n; i++) {
Array.set(values, i, reader.apply(buf));
}
return values;
}
/**
* default encode for Array
*/
static ByteBuf encodeAny(ByteBuf buf, T value, BiConsumer writer) {
if (value == null) return buf.writeByte(0);
buf.writeByte(1);
writer.accept(buf, value);
return buf;
}
/**
* default decode for Array
*/
@SuppressWarnings("unchecked")
static T decodeAny(ByteBuf buf, Function reader) {
if (buf.readByte() <= 0) return null;
return (T) reader.apply(buf);
}
/**
* default encode for Tuple
*
* @param size negative encode as null length, zero encode as zero length, both will put nothing.
*/
static ByteBuf encodeTuple(ByteBuf buf, int size, IntFunction