Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Extract Codec & friends from Util into separate modules #5801

Merged
merged 2 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions codec/api/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id 'java-library'
id 'io.deephaven.project.register'
}

description 'Codec API: The base package for column codecs'

dependencies {
compileOnly depAnnotations
}
1 change: 1 addition & 0 deletions codec/api/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.deephaven.project.ProjectType=JAVA_PUBLIC
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,12 @@ public interface ObjectCodec<TYPE> extends ObjectDecoder<TYPE> {

/**
* Encode the specified input as an array of bytes. Note that it is up to the implementation how to encode null
* inputs. The use of a zero-length byte array (e.g.
* {@link io.deephaven.datastructures.util.CollectionUtil#ZERO_LENGTH_BYTE_ARRAY}) is strongly encouraged.
* inputs. The use of a zero-length byte array is strongly encouraged.
*
* @param input The input object, possibly null
* @return The output byte array
*/
@NotNull
byte[] encode(@Nullable TYPE input);
byte @NotNull [] encode(@Nullable TYPE input);

/**
* Does this codec support encoding of null values?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
//
package io.deephaven.util.codec;

import io.deephaven.base.verify.Assert;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -36,7 +35,7 @@ public interface ObjectDecoder<TYPE> {
* @return The output object, possibly null
*/
@Nullable
TYPE decode(@NotNull byte[] input, int offset, int length);
TYPE decode(byte @NotNull [] input, int offset, int length);

/**
* Decode an object from a ByteBuffer. The position of the input buffer may or may not be modified by this method.
Expand Down Expand Up @@ -72,6 +71,9 @@ default TYPE decode(@NotNull final ByteBuffer buffer) {
*/
default void checkWidth(int actualWidth) throws IllegalArgumentException {
final int expectedWidth = expectedObjectWidth();
Assert.eq(expectedWidth, "expectedWidth", actualWidth, "actualWidth");
if (expectedWidth != actualWidth) {
throw new IllegalArgumentException(
"Expected width `" + expectedWidth + "` does not match actual width `" + actualWidth + "`");
}
}
}
18 changes: 18 additions & 0 deletions codec/builtin/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
id 'java-library'
id 'io.deephaven.project.register'
}

description 'Codec Builtin: Deephaven builtin codec implementations'

dependencies {
api project(":codec-api")

implementation project(":Base")
implementation project(":engine-query-constants")

compileOnly depAnnotations

Classpaths.inheritJUnitPlatform(project)
Classpaths.inheritJUnitClassic(project, 'testImplementation')
}
1 change: 1 addition & 0 deletions codec/builtin/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.deephaven.project.ProjectType=JAVA_PUBLIC
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
//
package io.deephaven.util.codec;

import io.deephaven.datastructures.util.CollectionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -73,18 +72,18 @@ public BigDecimalCodec(@Nullable String arguments) {
try {
int _precision = 0, _scale = 0; // zero indicates unlimited precision/scale, variable width encoding
boolean _strict = true;
if (arguments != null && arguments.trim().length() > 0) {
if (arguments != null && !arguments.trim().isEmpty()) {
final String[] tokens = arguments.split(",");
if (tokens.length > 0 && tokens[0].trim().length() > 0) {
if (tokens.length > 0 && !tokens[0].trim().isEmpty()) {
_precision = Integer.parseInt(tokens[0].trim());
if (_precision < 1) {
throw new IllegalArgumentException("Specified precision must be >= 1");
}
}
if (tokens.length > 1 && tokens[1].trim().length() > 0) {
if (tokens.length > 1 && !tokens[1].trim().isEmpty()) {
_scale = Integer.parseInt(tokens[1].trim());
}
if (tokens.length > 2 && tokens[2].trim().length() > 0) {
if (tokens.length > 2 && !tokens[2].trim().isEmpty()) {
String mode = tokens[2].trim();
switch (mode.toLowerCase()) {
case "allowrounding":
Expand Down Expand Up @@ -141,13 +140,12 @@ private void init() {
final byte[] unscaledZero = BigDecimal.ZERO.unscaledValue().toByteArray();
zeroBytes = new byte[Integer.BYTES + unscaledZero.length];
Arrays.fill(zeroBytes, (byte) 0);
nullBytes = CollectionUtil.ZERO_LENGTH_BYTE_ARRAY;
nullBytes = CodecUtil.ZERO_LENGTH_BYTE_ARRAY;
}
}

@NotNull
@Override
public byte[] encode(@Nullable final BigDecimal input) {
public byte @NotNull [] encode(@Nullable final BigDecimal input) {
if (input == null) {
return nullBytes;
}
Expand All @@ -173,7 +171,7 @@ public byte[] encode(@Nullable final BigDecimal input) {
// (i.e. too high a scale requires reducing precision to "make room")
if ((value.precision() > this.precision || value.scale() > scale)) {
if (strict) {
throw new IllegalArgumentException("Unable to encode value " + value.toString() + " with precision "
throw new IllegalArgumentException("Unable to encode value " + value + " with precision "
+ precision + " scale " + scale);
}
final int targetPrecision = Math.min(precision, value.precision() - Math.max(0, value.scale() - scale));
Expand All @@ -194,7 +192,7 @@ public byte[] encode(@Nullable final BigDecimal input) {
// copy unscaled bytes to proper size array
final byte[] unscaledValue = value.unscaledValue().toByteArray();
if (unscaledValue.length >= bytes.length) { // unscaled value must be at most one less than length of our buffer
throw new IllegalArgumentException("Value " + input.toString() + " is too large to encode with precision "
throw new IllegalArgumentException("Value " + input + " is too large to encode with precision "
+ precision + " and scale " + scale);
}

Expand All @@ -213,7 +211,7 @@ public byte[] encode(@Nullable final BigDecimal input) {

@Nullable
@Override
public BigDecimal decode(@NotNull final byte[] input, final int offset, final int length) {
public BigDecimal decode(final byte @NotNull [] input, final int offset, final int length) {

// variable size value
if (precision == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public BigIntegerCodec(@Nullable String arguments) {
// noinspection ConstantConditions
try {
int _precision = 0; // zero indicates unlimited precision, variable width encoding
if (arguments != null && arguments.trim().length() > 0) {
if (arguments != null && !arguments.trim().isEmpty()) {
_precision = Integer.parseInt(arguments.trim());
if (_precision < 1) {
throw new IllegalArgumentException("Specified precision must be >= 1");
Expand Down Expand Up @@ -61,17 +61,16 @@ public int getScale() {
return 0;
}

@NotNull
@Override
public byte[] encode(@Nullable final BigInteger input) {
public byte @NotNull [] encode(@Nullable final BigInteger input) {
return input == null
? codec.encodedNullValue()
: codec.encode(new BigDecimal(input));
}

@Nullable
@Override
public BigInteger decode(@NotNull final byte[] input, final int offset, final int length) {
public BigInteger decode(final byte @NotNull [] input, final int offset, final int length) {
final BigDecimal bd = codec.decode(input, offset, length);
return bd == null ? null : bd.toBigInteger();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,74 +1,23 @@
//
// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
//
package io.deephaven.util;
package io.deephaven.util.codec;

import io.deephaven.base.string.EncodingInfo;
import org.apache.commons.io.ByteOrderMark;
import org.jetbrains.annotations.NotNull;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Arrays;

public class EncodingUtil {
@SuppressWarnings("WeakerAccess")
public static final ByteOrderMark[] EMPTY_BOM_ARRAY = new ByteOrderMark[0];
class CodecUtil {
public static final byte[] ZERO_LENGTH_BYTE_ARRAY = new byte[0];

/**
* Get the {@link EncodingInfo} associated with a particular {@link Charset}
*
* @param charSet The charset
* @return the matching {@link EncodingInfo}
* @throws IllegalArgumentException if there is no associated encoding
*/
@NotNull
public static EncodingInfo getEncodingInfoForCharset(@NotNull Charset charSet) throws IllegalArgumentException {
return getEncodingInfoForCharset(charSet.name());
}

/**
* Get the {@link EncodingInfo} associated with a particular charset name
*
* @param charsetName the charset
* @return the matching {@link EncodingInfo}
* @throws IllegalArgumentException if there is no associated encoding
*/
public static EncodingInfo getEncodingInfoForCharset(@NotNull String charsetName) {
return Arrays.stream(EncodingInfo.values())
.filter(info -> info.getCharset().name().equals(charsetName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No EncodingInfo for " + charsetName));
}

/**
* Get an array containing the possible {@link ByteOrderMark byte order marks} that could be present within a file
* of the specified encoding. This is intended for use with {@link org.apache.commons.io.input.BOMInputStream}
*
* @param encoding The encoding.
* @return An array containing the possible {@link ByteOrderMark BOMs} for the encoding.
*/
@NotNull
public static ByteOrderMark[] getBOMsForEncoding(EncodingInfo encoding) {
switch (encoding) {
case UTF_8:
return new ByteOrderMark[] {ByteOrderMark.UTF_8};
case UTF_16BE:
return new ByteOrderMark[] {ByteOrderMark.UTF_16BE};
case UTF_16LE:
return new ByteOrderMark[] {ByteOrderMark.UTF_16LE};
case UTF_16:
return new ByteOrderMark[] {ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE};
}

return EMPTY_BOM_ARRAY;
}
private CodecUtil() {}

/**
* Encode the given string in UTF-8 format into the given ByteBuffer. The string is encoded as an int length
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.jetbrains.annotations.Nullable;

import java.io.*;
import java.lang.reflect.InvocationTargetException;

public class ExternalizableCodec<T extends Externalizable> implements ObjectCodec<T> {

Expand All @@ -21,9 +22,8 @@ public ExternalizableCodec(String className) {
}
}

@NotNull
@Override
public byte[] encode(@Nullable T input) {
public byte @NotNull [] encode(@Nullable T input) {
if (input == null) {
throw new UnsupportedOperationException(getClass() + " does not support null input");
}
Expand Down Expand Up @@ -55,16 +55,17 @@ public int getScale() {

@Nullable
@Override
public T decode(@NotNull byte[] input, int offset, int length) {
public T decode(byte @NotNull [] input, int offset, int length) {
try {
final ByteArrayInputStream byteInput = new ByteArrayInputStream(input, offset, length);
final ObjectInputStream objectInput = new ObjectInputStream(byteInput);
T result = externalizableClass.newInstance();
T result = externalizableClass.getDeclaredConstructor().newInstance();
result.readExternal(objectInput);
return result;
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,8 @@ public int getScale() {
return 0;
}

@NotNull
@Override
public byte[] encode(@Nullable final LocalDate input) {
public byte @NotNull [] encode(@Nullable final LocalDate input) {
if (input == null) {
if (nullBytes != null) {
return nullBytes;
Expand Down Expand Up @@ -157,7 +156,7 @@ public byte[] encode(@Nullable final LocalDate input) {

@Nullable
@Override
public LocalDate decode(@NotNull final byte[] input, final int offset, final int length) {
public LocalDate decode(final byte @NotNull [] input, final int offset, final int length) {
final int year, month, dayOfMonth;
if (input[offset] == NULL_INDICATOR) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,8 @@ public int getScale() {
return 0;
}

@NotNull
@Override
public byte[] encode(@Nullable final LocalTime input) {
public byte @NotNull [] encode(@Nullable final LocalTime input) {

if (input == null) {
if (nullBytes != null) {
Expand Down Expand Up @@ -130,7 +129,7 @@ public byte[] encode(@Nullable final LocalTime input) {

@Nullable
@Override
public LocalTime decode(@NotNull final byte[] input, final int offset, final int length) {
public LocalTime decode(final byte @NotNull [] input, final int offset, final int length) {
// test for null indicator (leading bit)
if ((input[offset] & NULL_INDICATOR) != 0) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
//
package io.deephaven.util.codec;

import io.deephaven.datastructures.util.CollectionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -23,7 +22,6 @@
*/
@SuppressWarnings("unused")
public abstract class MapCodec<K, V> implements ObjectCodec<Map<K, V>> {
private static final byte[] nullBytes = CollectionUtil.ZERO_LENGTH_BYTE_ARRAY;
private static final byte[] zeroBytes = new byte[4];

private static final int MINIMUM_SCRATCH_CAPACITY = 4096;
Expand All @@ -47,13 +45,12 @@ public int getScale() {
return 0;
}

@NotNull
@Override
public byte[] encode(@Nullable final Map<K, V> input) {
public byte @NotNull [] encode(@Nullable final Map<K, V> input) {
if (input == null) {
return nullBytes;
return CodecUtil.ZERO_LENGTH_BYTE_ARRAY;
}
if (input.size() == 0) {
if (input.isEmpty()) {
return zeroBytes;
}

Expand Down Expand Up @@ -137,7 +134,7 @@ public Map<K, V> decode(@NotNull final ByteBuffer byteBuffer) {

@Nullable
@Override
public Map<K, V> decode(@NotNull final byte[] input, final int offset, final int length) {
public Map<K, V> decode(final byte @NotNull [] input, final int offset, final int length) {
if (input.length == 0) {
return null;
}
Expand Down
Loading
Loading