Skip to content

Commit

Permalink
feat: Extract Codec & friends from Util into separate modules (#5727)
Browse files Browse the repository at this point in the history
This fixes #5726 by removing ObjectCodec and ObjectDecoder, their
implementations, as well as CodecCache and it's exception from the Util
module and into their own separate packages, codec-base, codec-builtin
and codec-cache.
  • Loading branch information
abaranec authored Jul 12, 2024
1 parent 3171fef commit 4fdad15
Show file tree
Hide file tree
Showing 38 changed files with 116 additions and 155 deletions.
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 libs.jetbrains.annotations
}
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 + "`");
}
}
}
17 changes: 17 additions & 0 deletions codec/builtin/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 libs.jetbrains.annotations

testImplementation libs.junit4
}
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

0 comments on commit 4fdad15

Please sign in to comment.