All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.deephaven.engine.table.impl.sources.ReinterpretUtils Maven / Gradle / Ivy

There is a newer version: 0.37.1
Show newest version
/**
 * Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending
 */
package io.deephaven.engine.table.impl.sources;

import io.deephaven.chunk.ChunkType;
import io.deephaven.engine.table.ColumnSource;
import io.deephaven.engine.table.WritableColumnSource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.function.Consumer;

public class ReinterpretUtils {

    /**
     * Given a {@code byte} column source turn it into a {@link Boolean} column source, either via reinterpretation or
     * wrapping.
     *
     * @param source the source to turn into a {@link Boolean} source
     *
     * @return the {@link Boolean} source
     */
    @NotNull
    public static ColumnSource byteToBooleanSource(@NotNull final ColumnSource source) {
        if (source.allowsReinterpret(Boolean.class)) {
            return source.reinterpret(Boolean.class);
        } else {
            return new ByteAsBooleanColumnSource(source);
        }
    }

    /**
     * Given a {@link Boolean} column source turn it into a {@code byte} column source, either via reinterpretation or
     * wrapping.
     *
     * @param source the source to turn into a {@code byte} source
     *
     * @return the {@code byte} source
     */
    @NotNull
    public static ColumnSource booleanToByteSource(@NotNull final ColumnSource source) {
        if (source.allowsReinterpret(byte.class)) {
            return source.reinterpret(byte.class);
        } else {
            return new BooleanAsByteColumnSource(source);
        }
    }

    /**
     * Given a writable {@link Boolean} column source turn it into a writable {@code byte} column source via
     * reinterpretation if possible.
     *
     * @param source the source to turn into a {@code byte} source
     *
     * @return the {@code byte} source or null if it could not be reinterpreted
     */
    @Nullable
    public static WritableColumnSource writableBooleanToByteSource(
            @NotNull final WritableColumnSource source) {
        if (source.allowsReinterpret(byte.class)) {
            return (WritableColumnSource) source.reinterpret(byte.class);
        }
        return null;
    }

    /**
     * Given a {@code long} column source turn it into an {@link Instant} column source, either via reinterpretation or
     * wrapping.
     *
     * @param source the source to turn into an {@link Instant} source
     *
     * @return the {@code long} source
     */
    @NotNull
    public static ColumnSource longToInstantSource(@NotNull final ColumnSource source) {
        if (source.allowsReinterpret(Instant.class)) {
            return source.reinterpret(Instant.class);
        } else {
            return new LongAsInstantColumnSource(source);
        }
    }

    /**
     * Given an {@link Instant} column source turn it into a {@code long} column source, either via reinterpretation or
     * wrapping.
     *
     * @param source the source to turn into a {@code long} source
     *
     * @return the {@code long} source
     */
    @NotNull
    public static ColumnSource instantToLongSource(@NotNull final ColumnSource source) {
        if (source.allowsReinterpret(long.class)) {
            return source.reinterpret(long.class);
        } else {
            return new InstantAsLongColumnSource(source);
        }
    }

    /**
     * Given a writable {@link Instant} column source turn it into a writable {@code long} column source via
     * reinterpretation if possible.
     *
     * @param source the source to turn into a {@code long} source
     *
     * @return the {@code long} source or null if it could not be reinterpreted
     */
    @Nullable
    public static WritableColumnSource writableInstantToLongSource(
            @NotNull final WritableColumnSource source) {
        if (source.allowsReinterpret(long.class)) {
            return (WritableColumnSource) source.reinterpret(long.class);
        }
        return null;
    }

    /**
     * Given a {@code long} column source turn it into a {@link ZonedDateTime} column source, either via
     * reinterpretation or wrapping.
     *
     * @param source the source to turn into a {@link ZonedDateTime} source
     * @param wrapperTimeZone the {@link ZoneId} to use if and only if we can't apply a simple reinterpret
     *
     * @return the {@code long} source
     */
    public static ColumnSource longToZonedDateTimeSource(
            @NotNull final ColumnSource source,
            @NotNull final ZoneId wrapperTimeZone) {
        if (source.allowsReinterpret(ZonedDateTime.class)) {
            return source.reinterpret(ZonedDateTime.class);
        } else {
            return new LongAsZonedDateTimeColumnSource(source, wrapperTimeZone);
        }
    }

    /**
     * Given a {@link ZonedDateTime} column source turn it into a {@code long} column source, either via
     * reinterpretation or wrapping.
     *
     * @param source the source to turn into a {@code long} source
     *
     * @return the {@code long} source
     */
    @NotNull
    public static ColumnSource zonedDateTimeToLongSource(@NotNull final ColumnSource source) {
        if (source.allowsReinterpret(long.class)) {
            return source.reinterpret(long.class);
        } else {
            return new ZonedDateTimeAsLongSource(source);
        }
    }

    /**
     * Given a writable {@link ZonedDateTime} column source turn it into a writable {@code long} column source via
     * reinterpretation if possible.
     *
     * @param source the source to turn into a {@code long} source
     *
     * @return the {@code long} source or null if it could not be reinterpreted
     */
    @Nullable
    public static WritableColumnSource writableZonedDateTimeToLongSource(
            @NotNull final WritableColumnSource source) {
        if (source.allowsReinterpret(long.class)) {
            return (WritableColumnSource) source.reinterpret(long.class);
        }
        return null;
    }

    /**
     * If source is something that we prefer to handle as a primitive, do the appropriate conversion.
     *
     * @param source The source to convert
     * @return if possible, the source converted to a primitive, otherwise the source
     */
    @SuppressWarnings("unchecked")
    @NotNull
    public static ColumnSource maybeConvertToPrimitive(@NotNull final ColumnSource source) {
        if (source.getType() == Boolean.class || source.getType() == boolean.class) {
            return booleanToByteSource((ColumnSource) source);
        }
        if (source.getType() == Instant.class) {
            return instantToLongSource((ColumnSource) source);
        }
        if (source.getType() == ZonedDateTime.class) {
            // We require this to be symmetrical with convertToOriginalType. This means we must restrict conversion to
            // sources where we can find the time zone when we need to convert back.
            // TODO (https://github.com/deephaven/deephaven-core/issues/3455): Do better with richer types
            if (source instanceof ConvertibleTimeSource.Zoned) {
                return zonedDateTimeToLongSource((ColumnSource) source);
            }
        }
        return source;
    }

    /**
     * If {@code source} is something that we prefer to handle as a primitive, do the appropriate conversion.
     *
     * @param source the source to convert
     * @return if possible, {@code source} converted to a writable primitive, otherwise {@code source}
     */
    @SuppressWarnings("unchecked")
    @NotNull
    public static WritableColumnSource maybeConvertToWritablePrimitive(
            @NotNull final WritableColumnSource source) {
        WritableColumnSource result = null;
        if (source.getType() == Boolean.class || source.getType() == boolean.class) {
            result = writableBooleanToByteSource((WritableColumnSource) source);
        } else if (source.getType() == Instant.class) {
            result = writableInstantToLongSource((WritableColumnSource) source);
        } else if (source.getType() == ZonedDateTime.class) {
            result = writableZonedDateTimeToLongSource((WritableColumnSource) source);
        }
        return result == null ? source : result;
    }

    /**
     * If {@code dataType} is something that we prefer to handle as a primitive, emit the appropriate {@link ChunkType},
     * else the normal ChunkType for the data type.
     *
     * @param dataType The data type to convert to a {@link ChunkType}
     * @return the appropriate {@link ChunkType} to use when extracting primitives from the source
     */
    @NotNull
    public static ChunkType maybeConvertToPrimitiveChunkType(@NotNull final Class dataType) {
        if (dataType == Boolean.class || dataType == boolean.class) {
            return ChunkType.Byte;
        }
        if (dataType == Instant.class || dataType == ZonedDateTime.class) {
            return ChunkType.Long;
        }
        return ChunkType.fromElementType(dataType);
    }

    /**
     * If {@code dataType} is something that we prefer to handle as a primitive, emit the appropriate {@link ChunkType},
     * else the normal ChunkType for the data type.
     *
     * @param dataType The data type to convert to a {@link ChunkType}
     * @return the appropriate {@link ChunkType} to use when writing primitives to the destination
     */
    @NotNull
    public static ChunkType maybeConvertToWritablePrimitiveChunkType(@NotNull final Class dataType) {
        if (dataType == Boolean.class || dataType == boolean.class) {
            return ChunkType.Byte;
        }
        if (dataType == Instant.class) {
            // Note that storing ZonedDateTime as a primitive is lossy on the time zone.
            return ChunkType.Long;
        }
        return ChunkType.fromElementType(dataType);
    }

    /**
     * If {@code dataType} is something that we prefer to handle as a primitive, emit the appropriate {@link Class data
     * type to use}, else return {@code dataType}.
     *
     * @param dataType The data type to examine
     * @return the appropriate data type to use when extracting primitives from the source
     */
    @NotNull
    public static Class maybeConvertToPrimitiveDataType(@NotNull final Class dataType) {
        if (dataType == Boolean.class || dataType == boolean.class) {
            return byte.class;
        }
        if (dataType == Instant.class || dataType == ZonedDateTime.class) {
            return long.class;
        }
        return dataType;
    }

    /**
     * Reinterpret or box {@code sourceToConvert} back to its original type.
     *
     * @param originalSource The source that was reinterpreted to produce {@code sourceToConvert}, or a similarly-typed
     *        source for type information
     * @param sourceToConvert The source to convert
     * @return reinterpret or box {@code sourceToConvert} back to the original type if possible
     * @throws UnsupportedOperationException for unsupported conversions
     */
    @NotNull
    public static ColumnSource convertToOriginalType(
            @NotNull final ColumnSource originalSource,
            @NotNull final ColumnSource sourceToConvert) {

        final Class originalType = originalSource.getType();
        final Consumer> validateSourceType = expectedType -> {
            if (sourceToConvert.getType() != expectedType) {
                throw new UnsupportedOperationException(String.format(
                        "Cannot convert column of type %s to %s", sourceToConvert.getType(), originalType));
            }
        };

        if (originalType == Boolean.class) {
            validateSourceType.accept(byte.class);
            // noinspection unchecked
            return byteToBooleanSource((ColumnSource) sourceToConvert);
        }

        if (originalType == Instant.class) {
            validateSourceType.accept(long.class);
            // noinspection unchecked
            return longToInstantSource((ColumnSource) sourceToConvert);
        }

        if (originalType == ZonedDateTime.class) {
            validateSourceType.accept(long.class);
            if (originalSource instanceof ConvertibleTimeSource.Zoned) {
                final ConvertibleTimeSource.Zoned zonedOriginal = (ConvertibleTimeSource.Zoned) originalSource;
                // noinspection unchecked
                return longToZonedDateTimeSource((ColumnSource) sourceToConvert, zonedOriginal.getZone());
            }
            throw new UnsupportedOperationException(String.format(
                    "Unsupported original source class %s for converting long to ZonedDateTime",
                    originalSource.getClass()));
        }

        throw new UnsupportedOperationException((String.format("Unsupported original type %s", originalType)));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy