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}