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

com.yahoo.slime.BinaryView Maven / Gradle / Ivy

Go to download

Library for use in Java components of Vespa. Shared code which do not fit anywhere else.

There is a newer version: 8.409.18
Show newest version
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.slime;

import java.util.function.Consumer;
import static com.yahoo.slime.BinaryFormat.decode_double;
import static com.yahoo.slime.BinaryFormat.decode_meta;
import static com.yahoo.slime.BinaryFormat.decode_type;
import static com.yahoo.slime.BinaryFormat.decode_zigzag;

/**
 * A read-only view of a Slime value that is stored in binary format.
 **/
public final class BinaryView implements Inspector {

    private final byte[] data;
    private final SymbolTable names;
    private final long[] index;
    private final int self;

    private BinaryView(byte[] data, SymbolTable names, long[] index, int self) {
        this.data = data;
        this.names = names;
        this.index = index;
        this.self = self;
    }
    private int byte_offset(int idx) {
        return (int)(index[idx] >> 33) & 0x7fff_ffff;
    }
    private int first_child(int idx) {
        return (int)(index[idx] >> 2) & 0x7fff_ffff;
    }
    private int ext_bits(int idx) {
        return (int)index[idx] & 0x3;
    }
    private int peek_cmpr_int(int idx) {
        long next = data[idx++];
        long value = (next & 0x7f);
        int shift = 7;
        while ((next & 0x80) != 0) {
            next = data[idx++];
            value |= ((next & 0x7f) << shift);
            shift += 7;
        }
        return (int)value;
    }
    private int skip_cmpr_int(int idx) {
        while ((data[idx++] & 0x80) != 0);
        return idx;
    }
    private int extract_children(int idx) {
        int bytes = decode_meta(data[idx++]);
        return (bytes == 0)
                ? peek_cmpr_int(idx)
                : (bytes - 1);
    }
    private long extract_long(int idx) {
        int bytes = decode_meta(data[idx++]);
        long value = 0;
        int shift = 0;
        for (int i = 0; i < bytes; ++i) {
            long b = data[idx++];
            value |= (b & 0xff) << shift;
            shift += 8;
        }
        return decode_zigzag(value);
    }
    private double extract_double(int idx) {
        int bytes = decode_meta(data[idx++]);
        long value = 0;
        int shift = 56;
        for (int i = 0; i < bytes; ++i) {
            long b = data[idx++];
            value |= (b & 0xff) << shift;
            shift -= 8;
        }
        return decode_double(value);
    }
    private String extract_string(int idx) {
        int bytes = decode_meta(data[idx++]);
        if (bytes == 0) {
            bytes = peek_cmpr_int(idx);
            idx = skip_cmpr_int(idx);
        } else {
            --bytes;
        }
        return Utf8Codec.decode(data, idx, bytes);
    }
    private byte[] extract_bytes(int idx) {
        int bytes = decode_meta(data[idx++]);
        if (bytes == 0) {
            bytes = peek_cmpr_int(idx);
            idx = skip_cmpr_int(idx);
        } else {
            --bytes;
        }
        byte[] ret = new byte[bytes];
        for (int i = 0; i < bytes; ++i) {
            ret[i] = data[idx++];
        }
        return ret;
    }
    private Inspector find_field(int pos, int len, int sym) {
        for (int i = 0; i < len; ++i) {
            int idx = byte_offset(pos + i);
            if (peek_cmpr_int(idx - (ext_bits(pos + i) + 1)) == sym) {
                return new BinaryView(data, names, index, pos + i);
            }
        }
        return NixValue.invalid();
    }

    @Override public boolean valid() { return true; }
    @Override public void ifValid(Consumer consumer) { consumer.accept(this); }
    @Override public Type type() { return decode_type(data[byte_offset(self)]); }
    @Override public int children() {
        return switch (type()) {
            case OBJECT, ARRAY -> extract_children(byte_offset(self));
            default -> 0;
        };
    }
    @Override public int entries() {
        return switch (type()) {
            case ARRAY -> extract_children(byte_offset(self));
            default -> 0;
        };
    }
    @Override public int fields() {
        return switch (type()) {
            case OBJECT -> extract_children(byte_offset(self));
            default -> 0;
        };
    }
    @Override public boolean asBool() {
        return switch (type()) {
            case BOOL -> (decode_meta(data[byte_offset(self)]) != 0);
            default -> false;
        };
    }
    @Override public long asLong() {
        return switch (type()) {
            case LONG -> extract_long(byte_offset(self));
            case DOUBLE -> (long)extract_double(byte_offset(self));
            default -> 0;
        };
    }
    @Override public double asDouble() {
        return switch (type()) {
            case LONG -> extract_long(byte_offset(self));
            case DOUBLE -> extract_double(byte_offset(self));
            default -> 0.0;
        };
    }
    @Override public String asString() {
        return switch (type()) {
            case STRING -> extract_string(byte_offset(self));
            default -> Value.emptyString;
        };
    }
    @Override public byte[] asUtf8() {
        return switch (type()) {
            case STRING -> extract_bytes(byte_offset(self));
            default -> Value.emptyData;
        };
    }
    @Override public byte[] asData() {
        return switch (type()) {
            case DATA -> extract_bytes(byte_offset(self));
            default -> Value.emptyData;
        };
    }
    @Override public void accept(Visitor v) {
        switch (type()) {
            case NIX:    v.visitNix(); break;
            case BOOL:   v.visitBool(decode_meta(data[byte_offset(self)]) != 0); break;
            case LONG:   v.visitLong(extract_long(byte_offset(self))); break;
            case DOUBLE: v.visitDouble(extract_double(byte_offset(self))); break;
            case STRING: v.visitString(extract_bytes(byte_offset(self))); break;
            case DATA:   v.visitData(extract_bytes(byte_offset(self))); break;
            case ARRAY:  v.visitArray(this); break;
            case OBJECT: v.visitObject(this); break;
            default: throw new RuntimeException("should not be reached");
        }
    }
    @Override public void traverse(ArrayTraverser at) {
        int pos = first_child(self);
        int len = entries();
        for (int i = 0; i < len; ++i) {
            at.entry(i, new BinaryView(data, names, index, pos + i));
        }
    }
    @Override public void traverse(ObjectSymbolTraverser ot) {
        int pos = first_child(self);
        int len = fields();
        for (int i = 0; i < len; ++i) {
            int sym = peek_cmpr_int(byte_offset(pos + i) - (ext_bits(pos + i) + 1));
            ot.field(sym, new BinaryView(data, names, index, pos + i));
        }
    }
    @Override public void traverse(ObjectTraverser ot) {
        int pos = first_child(self);
        int len = fields();
        for (int i = 0; i < len; ++i) {
            int sym = peek_cmpr_int(byte_offset(pos + i) - (ext_bits(pos + i) + 1));
            ot.field(names.inspect(sym), new BinaryView(data, names, index, pos + i));
        }
    }
    @Override public Inspector entry(int idx) {
        int limit = entries();
        if (idx >= 0 && idx < limit) {
            return new BinaryView(data, names, index, first_child(self) + idx);
        }
        return NixValue.invalid();
    }
    @Override public Inspector field(int sym) {
        int limit = fields();
        if (limit > 0 && sym != SymbolTable.INVALID) {
            return find_field(first_child(self), limit, sym);
        }
        return NixValue.invalid();
    }
    @Override public Inspector field(String name) {
        int limit = fields();
        if (limit > 0) {
            int sym = names.lookup(name);
            if (sym != SymbolTable.INVALID) {
                return find_field(first_child(self), limit, sym);
            }
        }
        return NixValue.invalid();
    }

    private static void buildIndex(BufferedInput input, DecodeIndex index, int self, int extBits) {
        int pos = input.getPosition();
        byte tag = input.getByte();
        Type type = decode_type(tag);
        int meta = decode_meta(tag);
        switch (type) {
            case NIX:
            case BOOL:
                index.set(self, pos, 0, extBits);
                break;
            case LONG:
            case DOUBLE:
                input.skip(meta);
                index.set(self, pos, 0, extBits);
                break;
            case STRING:
            case DATA: {
                int size = input.read_size(meta);
                input.skip(size);
                index.set(self, pos, 0, extBits);
                break; }
            case ARRAY: {
                int size = input.read_size(meta);
                int firstChild = index.tryReserveChildren(size, index.used() + 1, input.getPosition());
                if (firstChild < 0) {
                    input.fail("decode index too big");
                    return;
                }
                index.set(self, pos, firstChild, extBits);
                for (int i = 0; i < size; ++i) {
                    buildIndex(input, index, firstChild + i, 0);
                }
                break; }
            case OBJECT: {
                int size = input.read_size(meta);
                int firstChild = index.tryReserveChildren(size, index.used() + 1, input.getPosition());
                if (firstChild < 0) {
                    input.fail("decode index too big");
                    return;
                }
                index.set(self, pos, firstChild, extBits);
                for (int i = 0; i < size; ++i) {
                    int childExtBits = input.skip_cmpr_int();
                    if (childExtBits > 3) {
                        input.fail("symbol id too big");
                        return;
                    }
                    buildIndex(input, index, firstChild + i, childExtBits);
                }
                break; }
            default: throw new RuntimeException("should not be reached");
        }
    }

    public static Inspector inspect(byte[] data) {
        var input = new BufferedInput(data);
        var names = new SymbolTable();
        BinaryDecoder.decodeSymbolTable(input, names);
        var index = new DecodeIndex(input.getBacking().length, input.getPosition());
        buildIndex(input, index, 0, 0);
        if (input.failed()) {
            throw new IllegalArgumentException("bad input: " + input.getErrorMessage());
        }
        return new BinaryView(input.getBacking(), names, index.getBacking(), 0);
    }

    static int peek_cmpr_int_for_testing(byte[] data, int idx) {
        return new BinaryView(data, null, null, -1).peek_cmpr_int(idx);
    }
    static int skip_cmpr_int_for_testing(byte[] data, int idx) {
        return new BinaryView(data, null, null, -1).skip_cmpr_int(idx);
    }
    static int extract_children_for_testing(byte[] data, int idx) {
        return new BinaryView(data, null, null, -1).extract_children(idx);
    }
    static long extract_long_for_testing(byte[] data, int idx) {
        return new BinaryView(data, null, null, -1).extract_long(idx);
    }
    static double extract_double_for_testing(byte[] data, int idx) {
        return new BinaryView(data, null, null, -1).extract_double(idx);
    }
    static int byte_offset_for_testing(DecodeIndex index, int idx) {
        return new BinaryView(null, null, index.getBacking(), -1).byte_offset(idx);
    }
    static int first_child_for_testing(DecodeIndex index, int idx) {
        return new BinaryView(null, null, index.getBacking(), -1).first_child(idx);
    }
    static int ext_bits_for_testing(DecodeIndex index, int idx) {
        return new BinaryView(null, null, index.getBacking(), -1).ext_bits(idx);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy