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

org.neo4j.values.storable.ValueByteBufferCodec Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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, see .
 */
package org.neo4j.values.storable;

import static java.time.ZoneOffset.UTC;
import static org.neo4j.util.Preconditions.checkArgument;
import static org.neo4j.util.Preconditions.checkState;
import static org.neo4j.values.storable.DateValue.epochDate;
import static org.neo4j.values.storable.Values.booleanArray;
import static org.neo4j.values.storable.Values.booleanValue;
import static org.neo4j.values.storable.Values.byteArray;
import static org.neo4j.values.storable.Values.byteValue;
import static org.neo4j.values.storable.Values.charArray;
import static org.neo4j.values.storable.Values.charValue;
import static org.neo4j.values.storable.Values.dateArray;
import static org.neo4j.values.storable.Values.dateTimeArray;
import static org.neo4j.values.storable.Values.doubleArray;
import static org.neo4j.values.storable.Values.doubleValue;
import static org.neo4j.values.storable.Values.durationArray;
import static org.neo4j.values.storable.Values.floatArray;
import static org.neo4j.values.storable.Values.floatValue;
import static org.neo4j.values.storable.Values.intArray;
import static org.neo4j.values.storable.Values.intValue;
import static org.neo4j.values.storable.Values.localDateTimeArray;
import static org.neo4j.values.storable.Values.localTimeArray;
import static org.neo4j.values.storable.Values.longArray;
import static org.neo4j.values.storable.Values.longValue;
import static org.neo4j.values.storable.Values.pointArray;
import static org.neo4j.values.storable.Values.pointValue;
import static org.neo4j.values.storable.Values.shortArray;
import static org.neo4j.values.storable.Values.shortValue;
import static org.neo4j.values.storable.Values.stringArray;
import static org.neo4j.values.storable.Values.stringValue;
import static org.neo4j.values.storable.Values.timeArray;

import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.graphdb.Resource;
import org.neo4j.internal.helpers.ArrayUtil;
import org.neo4j.values.utils.TemporalUtil;

public final class ValueByteBufferCodec {
    private ValueByteBufferCodec() {}

    public interface ByteBufferAllocator {
        ByteBuffer allocate(long capacity);

        void free();
    }

    public static class Writer implements ValueWriter, Resource {
        private final ByteBufferAllocator allocation;
        private ByteBuffer buf;

        public Writer(int chunkSize, ByteBufferAllocator allocation) {
            this.allocation = allocation;
            this.buf = allocation.allocate(chunkSize);
        }

        @Override
        public void close() {
            allocation.free();
            buf = null;
        }

        public ByteBuffer write(Value value) {
            checkState(buf != null, "Writer is closed");
            try {
                buf.clear();
                buf.put((byte) ValueByteBufferCodec.ValueType.forValue(value).ordinal());
                value.writeTo(this);
                buf.flip();
                return buf;
            } catch (BufferOverflowException e) {
                final int newSize = newSizeGrow();
                allocation.free();
                buf = allocation.allocate(newSize);
                return write(value);
            }
        }

        private int newSizeGrow() {
            long old = buf.capacity();
            if (old == ArrayUtil.MAX_ARRAY_SIZE) {
                throw new RuntimeException("Unable to allocate array bigger than " + ArrayUtil.MAX_ARRAY_SIZE);
            }
            return Math.toIntExact(Math.min(old * 2, ArrayUtil.MAX_ARRAY_SIZE));
        }

        @Override
        public void writeNull() {
            // nop
        }

        @Override
        public void writeBoolean(boolean value) {
            buf.put((byte) (value ? 1 : 0));
        }

        @Override
        public void writeInteger(byte value) {
            buf.put(value);
        }

        @Override
        public void writeInteger(short value) {
            buf.putShort(value);
        }

        @Override
        public void writeInteger(int value) {
            buf.putInt(value);
        }

        @Override
        public void writeInteger(long value) {
            buf.putLong(value);
        }

        @Override
        public void writeFloatingPoint(float value) {
            buf.putFloat(value);
        }

        @Override
        public void writeFloatingPoint(double value) {
            buf.putDouble(value);
        }

        @Override
        public void writeString(String value) {
            final int len = value.length();
            buf.putInt(value.length());
            for (int i = 0; i < len; i++) {
                final char c = value.charAt(i);
                buf.putChar(c);
            }
        }

        @Override
        public void writeString(char value) {
            buf.putChar(value);
        }

        @Override
        public void beginArray(int size, ArrayType arrayType) {
            buf.putInt(size);
        }

        @Override
        public void endArray() {
            // nop
        }

        @Override
        public void writeByteArray(byte[] value) {
            buf.putInt(value.length);
            buf.put(value);
        }

        @Override
        public void writePoint(CoordinateReferenceSystem crs, double[] coordinate) {
            checkArgument(
                    coordinate.length == crs.getDimension(),
                    "Dimension for %s is %d, got %d",
                    crs.getName(),
                    crs.getDimension(),
                    coordinate.length);
            buf.putInt(crs.getCode());
            for (int i = 0; i < crs.getDimension(); i++) {
                buf.putDouble(coordinate[i]);
            }
        }

        @Override
        public void writeDuration(long months, long days, long seconds, int nanos) {
            buf.putLong(months);
            buf.putLong(days);
            buf.putLong(seconds);
            buf.putInt(nanos);
        }

        @Override
        public void writeDate(LocalDate localDate) {
            buf.putLong(localDate.toEpochDay());
        }

        @Override
        public void writeLocalTime(LocalTime localTime) {
            buf.putLong(localTime.toNanoOfDay());
        }

        @Override
        public void writeTime(OffsetTime offsetTime) {
            buf.putLong(TemporalUtil.getNanosOfDayUTC(offsetTime));
            buf.putInt(offsetTime.getOffset().getTotalSeconds());
        }

        @Override
        public void writeLocalDateTime(LocalDateTime localDateTime) {
            buf.putLong(localDateTime.toEpochSecond(UTC));
            buf.putInt(localDateTime.getNano());
        }

        @Override
        public void writeDateTime(ZonedDateTime zonedDateTime) {
            buf.putLong(zonedDateTime.toEpochSecond());
            buf.putInt(zonedDateTime.getNano());

            final ZoneId zone = zonedDateTime.getZone();
            if (zone instanceof ZoneOffset) {
                final int offsetSeconds = ((ZoneOffset) zone).getTotalSeconds();
                // lowest bit set to 0: it's a zone offset in seconds
                buf.putInt(offsetSeconds << 1);
            } else {
                // lowest bit set to 1: it's a zone id
                final int zoneId = (TimeZones.map(zone.getId()) << 1) | 1;
                buf.putInt(zoneId);
            }
        }
    }

    private static BooleanValue readBoolean(ByteBuffer chunk, int offset) {
        return booleanValue(chunk.get(offset) != 0);
    }

    private static BooleanArray readBooleanArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final boolean[] array = new boolean[len];
        for (int i = 0; i < len; i++) {
            array[i] = bb.get(offset) != 0;
            ++offset;
        }
        return booleanArray(array);
    }

    private static ByteValue readByte(ByteBuffer chunk, int offset) {
        return byteValue(chunk.get(offset));
    }

    private static ByteArray readByteArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final byte[] array = new byte[len];
        for (int i = 0; i < len; i++) {
            array[i] = bb.get(offset);
            ++offset;
        }
        return byteArray(array);
    }

    private static CharValue readChar(ByteBuffer chunk, int offset) {
        return charValue(chunk.getChar(offset));
    }

    private static CharArray readCharArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final char[] array = new char[len];
        for (int i = 0; i < len; i++) {
            array[i] = bb.getChar(offset);
            offset += Character.BYTES;
        }
        return charArray(array);
    }

    private static DateValue readDate(ByteBuffer chunk, int offset) {
        return epochDate(chunk.getLong(offset));
    }

    private static ArrayValue readDateArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final LocalDate[] array = new LocalDate[len];
        for (int i = 0; i < len; i++) {
            array[i] = LocalDate.ofEpochDay(bb.getLong(offset));
            offset += Long.BYTES;
        }
        return dateArray(array);
    }

    private static DoubleValue readDouble(ByteBuffer chunk, int offset) {
        return doubleValue(chunk.getDouble(offset));
    }

    private static DoubleArray readDoubleArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final double[] array = new double[len];
        for (int i = 0; i < len; i++) {
            array[i] = bb.getDouble(offset);
            offset += Long.BYTES;
        }
        return doubleArray(array);
    }

    private static DurationValue readDuration(ByteBuffer bb, int offset) {
        final long months = bb.getLong(offset);
        offset += Long.BYTES;
        final long days = bb.getLong(offset);
        offset += Long.BYTES;
        final long seconds = bb.getLong(offset);
        offset += Long.BYTES;
        final int nanos = bb.getInt(offset);
        return DurationValue.duration(months, days, seconds, nanos);
    }

    private static ArrayValue readDurationArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final DurationValue[] array = new DurationValue[len];
        for (int i = 0; i < len; i++) {
            array[i] = readDuration(bb, offset);
            offset += 3 * Long.BYTES + Integer.BYTES;
        }
        return durationArray(array);
    }

    private static FloatValue readFloat(ByteBuffer chunk, int offset) {
        return floatValue(chunk.getFloat(offset));
    }

    private static FloatArray readFloatArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        float[] array = new float[len];
        for (int i = 0; i < len; i++) {
            array[i] = bb.getFloat(offset);
            offset += Float.BYTES;
        }
        return floatArray(array);
    }

    private static IntValue readInt(ByteBuffer chunk, int offset) {
        return intValue(chunk.getInt(offset));
    }

    private static IntArray readIntArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final int[] array = new int[len];
        for (int i = 0; i < len; i++) {
            array[i] = bb.getInt(offset);
            offset += Integer.BYTES;
        }
        return intArray(array);
    }

    private static LocalDateTimeValue readLocalDateTime(ByteBuffer bb, int offset) {
        final long epochSecond = bb.getLong(offset);
        offset += Long.BYTES;
        final int nanos = bb.getInt(offset);
        return LocalDateTimeValue.localDateTime(epochSecond, nanos);
    }

    private static ArrayValue readLocalDateTimeArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final LocalDateTime[] array = new LocalDateTime[len];
        for (int i = 0; i < len; i++) {
            final long epochSecond = bb.getLong(offset);
            offset += Long.BYTES;
            final int nanos = bb.getInt(offset);
            offset += Integer.BYTES;
            array[i] = LocalDateTime.ofEpochSecond(epochSecond, nanos, UTC);
        }
        return localDateTimeArray(array);
    }

    private static LocalTimeValue readLocalTime(ByteBuffer chunk, int offset) {
        return LocalTimeValue.localTime(chunk.getLong(offset));
    }

    private static ArrayValue readLocalTimeArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final LocalTime[] array = new LocalTime[len];
        for (int i = 0; i < len; i++) {
            array[i] = LocalTime.ofNanoOfDay(bb.getLong(offset));
            offset += Long.BYTES;
        }
        return localTimeArray(array);
    }

    private static LongValue readLong(ByteBuffer chunk, int offset) {
        return longValue(chunk.getLong(offset));
    }

    private static LongArray readLongArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final long[] array = new long[len];
        for (int i = 0; i < len; i++) {
            array[i] = bb.getLong(offset);
            offset += Long.BYTES;
        }
        return longArray(array);
    }

    private static PointValue readPoint(ByteBuffer chunk, int offset) {
        final int crsCode = chunk.getInt(offset);
        offset += Integer.BYTES;
        final CoordinateReferenceSystem crs = CoordinateReferenceSystem.get(crsCode);
        final double[] coordinate = new double[crs.getDimension()];
        for (int i = 0; i < coordinate.length; i++) {
            coordinate[i] = chunk.getDouble(offset);
            offset += Double.BYTES;
        }
        return pointValue(crs, coordinate);
    }

    private static PointArray readPointArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final PointValue[] array = new PointValue[len];
        for (int i = 0; i < len; i++) {
            final PointValue point = readPoint(bb, offset);
            array[i] = point;
            offset += Integer.BYTES + point.getCoordinateReferenceSystem().getDimension() * Double.BYTES;
        }
        return pointArray(array);
    }

    private static String readRawString(ByteBuffer chunk, int offset) {
        final int len = chunk.getInt(offset);
        if (len == 0) {
            return StringUtils.EMPTY;
        }
        offset += Integer.BYTES;

        final char[] chars = new char[len];
        for (int i = 0; i < len; i++) {
            chars[i] = chunk.getChar(offset);
            offset += Character.BYTES;
        }
        return new String(chars);
    }

    private static ShortValue readShort(ByteBuffer chunk, int offset) {
        return shortValue(chunk.getShort(offset));
    }

    private static ShortArray readShortArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final short[] array = new short[len];
        for (int i = 0; i < len; i++) {
            array[i] = bb.getShort(offset);
            offset += Short.BYTES;
        }
        return shortArray(array);
    }

    private static TextValue readString(ByteBuffer chunk, int offset) {
        return stringValue(readRawString(chunk, offset));
    }

    private static ArrayValue readStringArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;

        final String[] array = new String[len];
        for (int i = 0; i < len; i++) {
            final String str = readRawString(bb, offset);
            array[i] = str;
            offset += Integer.BYTES + str.length() * Character.BYTES;
        }
        return stringArray(array);
    }

    private static TimeValue readTime(ByteBuffer bb, int offset) {
        return TimeValue.time(readRawTime(bb, offset));
    }

    private static ArrayValue readTimeArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final OffsetTime[] array = new OffsetTime[len];
        for (int i = 0; i < len; i++) {
            array[i] = readRawTime(bb, offset);
            offset += Long.BYTES + Integer.BYTES;
        }
        return timeArray(array);
    }

    private static OffsetTime readRawTime(ByteBuffer bb, int offset) {
        final long nanosOfDayUTC = bb.getLong(offset);
        offset += Long.BYTES;
        final int offsetSeconds = bb.getInt(offset);
        return OffsetTime.ofInstant(Instant.ofEpochSecond(0, nanosOfDayUTC), ZoneOffset.ofTotalSeconds(offsetSeconds));
    }

    private static DateTimeValue readDateTime(ByteBuffer bb, int offset) {
        final long epocSeconds = bb.getLong(offset);
        offset += Long.BYTES;
        final int nanos = bb.getInt(offset);
        offset += Integer.BYTES;
        final int z = bb.getInt(offset);
        return DateTimeValue.datetime(epocSeconds, nanos, toZoneId(z));
    }

    private static ZoneId toZoneId(int z) {
        // if lowest bit is set to 1 then it's a shifted zone id
        if ((z & 1) != 0) {
            final String zoneId = TimeZones.map((short) (z >> 1));
            return ZoneId.of(zoneId);
        }
        // otherwise it's a shifted offset seconds value
        // preserve sign bit for negative offsets
        return ZoneOffset.ofTotalSeconds(z >> 1);
    }

    private static ArrayValue readDateTimeArray(ByteBuffer bb, int offset) {
        final int len = bb.getInt(offset);
        offset += Integer.BYTES;
        final ZonedDateTime[] array = new ZonedDateTime[len];
        for (int i = 0; i < len; i++) {
            final long epocSeconds = bb.getLong(offset);
            offset += Long.BYTES;
            final int nanos = bb.getInt(offset);
            offset += Integer.BYTES;
            final int z = bb.getInt(offset);
            offset += Integer.BYTES;
            array[i] = ZonedDateTime.ofInstant(Instant.ofEpochSecond(epocSeconds, nanos), toZoneId(z));
        }
        return dateTimeArray(array);
    }

    public enum ValueType {
        NO_VALUE(NoValue.class, (unused, unused2) -> Values.NO_VALUE),
        BOOLEAN(BooleanValue.class, ValueByteBufferCodec::readBoolean),
        BOOLEAN_ARRAY(BooleanArray.class, ValueByteBufferCodec::readBooleanArray),
        BYTE(ByteValue.class, ValueByteBufferCodec::readByte),
        BYTE_ARRAY(ByteArray.class, ValueByteBufferCodec::readByteArray),
        SHORT(ShortValue.class, ValueByteBufferCodec::readShort),
        SHORT_ARRAY(ShortArray.class, ValueByteBufferCodec::readShortArray),
        INT(IntValue.class, ValueByteBufferCodec::readInt),
        INT_ARRAY(IntArray.class, ValueByteBufferCodec::readIntArray),
        LONG(LongValue.class, ValueByteBufferCodec::readLong),
        LONG_ARRAY(LongArray.class, ValueByteBufferCodec::readLongArray),
        FLOAT(FloatValue.class, ValueByteBufferCodec::readFloat),
        FLOAT_ARRAY(FloatArray.class, ValueByteBufferCodec::readFloatArray),
        DOUBLE(DoubleValue.class, ValueByteBufferCodec::readDouble),
        DOUBLE_ARRAY(DoubleArray.class, ValueByteBufferCodec::readDoubleArray),
        STRING(StringValue.class, ValueByteBufferCodec::readString),
        STRING_ARRAY(StringArray.class, ValueByteBufferCodec::readStringArray),
        CHAR(CharValue.class, ValueByteBufferCodec::readChar),
        CHAR_ARRAY(CharArray.class, ValueByteBufferCodec::readCharArray),
        POINT(PointValue.class, ValueByteBufferCodec::readPoint),
        POINT_ARRAY(PointArray.class, ValueByteBufferCodec::readPointArray),
        DURATION(DurationValue.class, ValueByteBufferCodec::readDuration),
        DURATION_ARRAY(DurationArray.class, ValueByteBufferCodec::readDurationArray),
        DATE(DateValue.class, ValueByteBufferCodec::readDate),
        DATE_ARRAY(DateArray.class, ValueByteBufferCodec::readDateArray),
        TIME(TimeValue.class, ValueByteBufferCodec::readTime),
        TIME_ARRAY(TimeArray.class, ValueByteBufferCodec::readTimeArray),
        DATE_TIME(DateTimeValue.class, ValueByteBufferCodec::readDateTime),
        DATE_TIME_ARRAY(DateTimeArray.class, ValueByteBufferCodec::readDateTimeArray),
        LOCAL_TIME(LocalTimeValue.class, ValueByteBufferCodec::readLocalTime),
        LOCAL_TIME_ARRAY(LocalTimeArray.class, ValueByteBufferCodec::readLocalTimeArray),
        LOCAL_DATE_TIME(LocalDateTimeValue.class, ValueByteBufferCodec::readLocalDateTime),
        LOCAL_DATE_TIME_ARRAY(LocalDateTimeArray.class, ValueByteBufferCodec::readLocalDateTimeArray),
        ;

        private final Class valueClass;

        private final ValueReader reader;

         ValueType(Class valueClass, ValueReader reader) {
            this.valueClass = valueClass;
            this.reader = reader;
        }

        private static ValueType forValue(Value value) {
            for (ValueType valueType : VALUE_TYPES) {
                if (valueType.valueClass.isAssignableFrom(value.getClass())) {
                    return valueType;
                }
            }
            throw new IllegalArgumentException("Unsupported value type: " + value.getClass());
        }

        public ValueReader getReader() {
            return reader;
        }
    }

    @FunctionalInterface
    public interface ValueReader {
        T read(ByteBuffer bb, int offset);
    }

    public static final ValueType[] VALUE_TYPES = ValueType.values();

    public static Value readValue(ByteBuffer buffer) {
        byte valueTypeOrdinal = buffer.get();
        var type = VALUE_TYPES[valueTypeOrdinal];
        return type.getReader().read(buffer, buffer.position());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy