001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.protobuf;
003
004import java.io.IOException;
005import java.nio.charset.StandardCharsets;
006import java.util.stream.Stream;
007
008import org.openstreetmap.josm.tools.Utils;
009
010/**
011 * A protobuf record, storing the {@link WireType}, the parsed field number, and the bytes for it.
012 *
013 * @author Taylor Smock
014 * @since 17862
015 */
016public class ProtobufRecord implements AutoCloseable {
017    private static final byte[] EMPTY_BYTES = {};
018    private final WireType type;
019    private final int field;
020    private byte[] bytes;
021
022    /**
023     * Create a new Protobuf record
024     *
025     * @param parser The parser to use to create the record
026     * @throws IOException - if an IO error occurs
027     */
028    public ProtobufRecord(ProtobufParser parser) throws IOException {
029        Number number = ProtobufParser.convertByteArray(parser.nextVarInt(), ProtobufParser.VAR_INT_BYTE_SIZE);
030        // I don't foresee having field numbers > {@code Integer#MAX_VALUE >> 3}
031        this.field = (int) number.longValue() >> 3;
032        // 7 is 111 (so last three bits)
033        byte wireType = (byte) (number.longValue() & 7);
034        this.type = Stream.of(WireType.values()).filter(wType -> wType.getTypeRepresentation() == wireType).findFirst()
035          .orElse(WireType.UNKNOWN);
036
037        if (this.type == WireType.VARINT) {
038            this.bytes = parser.nextVarInt();
039        } else if (this.type == WireType.SIXTY_FOUR_BIT) {
040            this.bytes = parser.nextFixed64();
041        } else if (this.type == WireType.THIRTY_TWO_BIT) {
042            this.bytes = parser.nextFixed32();
043        } else if (this.type == WireType.LENGTH_DELIMITED) {
044            this.bytes = parser.nextLengthDelimited();
045        } else {
046            this.bytes = EMPTY_BYTES;
047        }
048    }
049
050    /**
051     * Get as a double ({@link WireType#SIXTY_FOUR_BIT})
052     *
053     * @return the double
054     */
055    public double asDouble() {
056        long doubleNumber = ProtobufParser.convertByteArray(asFixed64(), ProtobufParser.BYTE_SIZE).longValue();
057        return Double.longBitsToDouble(doubleNumber);
058    }
059
060    /**
061     * Get as 32 bits ({@link WireType#THIRTY_TWO_BIT})
062     *
063     * @return a byte array of the 32 bits (4 bytes)
064     */
065    public byte[] asFixed32() {
066        // TODO verify, or just assume?
067        // 4 bytes == 32 bits
068        return this.bytes;
069    }
070
071    /**
072     * Get as 64 bits ({@link WireType#SIXTY_FOUR_BIT})
073     *
074     * @return a byte array of the 64 bits (8 bytes)
075     */
076    public byte[] asFixed64() {
077        // TODO verify, or just assume?
078        // 8 bytes == 64 bits
079        return this.bytes;
080    }
081
082    /**
083     * Get as a float ({@link WireType#THIRTY_TWO_BIT})
084     *
085     * @return the float
086     */
087    public float asFloat() {
088        int floatNumber = ProtobufParser.convertByteArray(asFixed32(), ProtobufParser.BYTE_SIZE).intValue();
089        return Float.intBitsToFloat(floatNumber);
090    }
091
092    /**
093     * Get the signed var int ({@code WireType#VARINT}).
094     * These are specially encoded so that they take up less space.
095     *
096     * @return The signed var int ({@code sint32} or {@code sint64})
097     */
098    public Number asSignedVarInt() {
099        final Number signed = this.asUnsignedVarInt();
100        return ProtobufParser.decodeZigZag(signed);
101    }
102
103    /**
104     * Get as a string ({@link WireType#LENGTH_DELIMITED})
105     *
106     * @return The string (encoded as {@link StandardCharsets#UTF_8})
107     */
108    public String asString() {
109        return Utils.intern(new String(this.bytes, StandardCharsets.UTF_8));
110    }
111
112    /**
113     * Get the var int ({@code WireType#VARINT})
114     *
115     * @return The var int ({@code int32}, {@code int64}, {@code uint32}, {@code uint64}, {@code bool}, {@code enum})
116     */
117    public Number asUnsignedVarInt() {
118        return ProtobufParser.convertByteArray(this.bytes, ProtobufParser.VAR_INT_BYTE_SIZE);
119    }
120
121    @Override
122    public void close() {
123        this.bytes = null;
124    }
125
126    /**
127     * Get the raw bytes for this record
128     *
129     * @return The bytes
130     */
131    public byte[] getBytes() {
132        return this.bytes;
133    }
134
135    /**
136     * Get the field value
137     *
138     * @return The field value
139     */
140    public int getField() {
141        return this.field;
142    }
143
144    /**
145     * Get the WireType of the data
146     *
147     * @return The {@link WireType} of the data
148     */
149    public WireType getType() {
150        return this.type;
151    }
152}