Ticket #17177: 17177.7.protobuf.patch
File 17177.7.protobuf.patch, 62.3 KB (added by , 3 years ago) |
---|
-
src/org/openstreetmap/josm/data/protobuf/ProtoBufPacked.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import java.util.ArrayList; 5 import java.util.List; 6 7 /** 8 * Parse packed values (only numerical values) 9 * 10 * @author Taylor Smock 11 * @since xxx 12 */ 13 public class ProtoBufPacked { 14 private final byte[] bytes; 15 private final Number[] numbers; 16 private int location; 17 18 /** 19 * Create a new ProtoBufPacked object 20 * 21 * @param bytes The packed bytes 22 */ 23 public ProtoBufPacked(byte[] bytes) { 24 this.location = 0; 25 this.bytes = bytes; 26 List<Number> numbersT = new ArrayList<>(); 27 while (this.location < bytes.length) { 28 numbersT.add(ProtoBufParser.convertByteArray(this.nextVarInt(), ProtoBufParser.VAR_INT_BYTE_SIZE)); 29 } 30 31 this.numbers = new Number[numbersT.size()]; 32 for (int i = 0; i < numbersT.size(); i++) { 33 this.numbers[i] = numbersT.get(i); 34 } 35 } 36 37 /** 38 * Get the parsed number array 39 * 40 * @return The number array 41 */ 42 public Number[] getArray() { 43 return this.numbers; 44 } 45 46 private byte[] nextVarInt() { 47 List<Byte> byteList = new ArrayList<>(); 48 while ((this.bytes[this.location] & ProtoBufParser.MOST_SIGNIFICANT_BYTE) 49 == ProtoBufParser.MOST_SIGNIFICANT_BYTE) { 50 // Get rid of the leading bit (shift left 1, then shift right 1 unsigned) 51 byteList.add((byte) (this.bytes[this.location++] ^ ProtoBufParser.MOST_SIGNIFICANT_BYTE)); 52 } 53 // The last byte doesn't drop the most significant bit 54 byteList.add(this.bytes[this.location++]); 55 byte[] byteArray = new byte[byteList.size()]; 56 for (int i = 0; i < byteList.size(); i++) { 57 byteArray[i] = byteList.get(i); 58 } 59 60 return byteArray; 61 } 62 } -
src/org/openstreetmap/josm/data/protobuf/ProtoBufParser.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import java.io.BufferedInputStream; 5 import java.io.ByteArrayInputStream; 6 import java.io.IOException; 7 import java.io.InputStream; 8 import java.util.ArrayList; 9 import java.util.Collection; 10 import java.util.List; 11 12 import org.openstreetmap.josm.tools.Logging; 13 14 /** 15 * A basic Protobuf parser 16 * 17 * @author Taylor Smock 18 * @since xxx 19 */ 20 public class ProtoBufParser implements AutoCloseable { 21 /** 22 * The default byte size (see {@link #VAR_INT_BYTE_SIZE} for var ints) 23 */ 24 public static final byte BYTE_SIZE = 8; 25 /** 26 * The byte size for var ints (since the first byte is just an indicator for if the var int is done) 27 */ 28 public static final byte VAR_INT_BYTE_SIZE = BYTE_SIZE - 1; 29 /** 30 * Used to get the most significant byte 31 */ 32 static final byte MOST_SIGNIFICANT_BYTE = (byte) (1 << 7); 33 /** 34 * Convert a byte array to a number (little endian) 35 * 36 * @param bytes The bytes to convert 37 * @param byteSize The size of the byte. For var ints, this is 7, for other ints, this is 8. 38 * @return An appropriate {@link Number} class. 39 */ 40 public static Number convertByteArray(byte[] bytes, byte byteSize) { 41 long number = 0; 42 for (int i = 0; i < bytes.length; i++) { 43 // Need to convert to uint64 in order to avoid bit operation from filling in 1's and overflow issues 44 number += Byte.toUnsignedLong(bytes[i]) << (byteSize * i); 45 } 46 return convertLong(number); 47 } 48 49 /** 50 * Convert a long to an appropriate {@link Number} class 51 * 52 * @param number The long to convert 53 * @return A {@link Number} 54 */ 55 public static Number convertLong(long number) { 56 // TODO deal with booleans 57 if (number <= Byte.MAX_VALUE && number >= Byte.MIN_VALUE) { 58 return (byte) number; 59 } else if (number <= Short.MAX_VALUE && number >= Short.MIN_VALUE) { 60 return (short) number; 61 } else if (number <= Integer.MAX_VALUE && number >= Integer.MIN_VALUE) { 62 return (int) number; 63 } 64 return number; 65 } 66 67 /** 68 * Decode a zig-zag encoded value 69 * 70 * @param signed The value to decode 71 * @return The decoded value 72 */ 73 public static Number decodeZigZag(Number signed) { 74 final long value = signed.longValue(); 75 return convertLong((value >> 1) ^ -(value & 1)); 76 } 77 78 /** 79 * Encode a number to a zig-zag encode value 80 * 81 * @param signed The number to encode 82 * @return The encoded value 83 */ 84 public static Number encodeZigZag(Number signed) { 85 final long value = signed.longValue(); 86 // This boundary condition could be >= or <= or both. Tests indicate that it doesn't actually matter. 87 // The only difference would be the number type returned, except it is always converted to the most basic type. 88 final int shift = (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE ? Long.BYTES : Integer.BYTES) * 8 - 1; 89 return convertLong((value << 1) ^ (value >> shift)); 90 } 91 92 private final InputStream inputStream; 93 94 /** 95 * Create a new parser 96 * 97 * @param bytes The bytes to parse 98 */ 99 public ProtoBufParser(byte[] bytes) { 100 this(new ByteArrayInputStream(bytes)); 101 } 102 103 /** 104 * Create a new parser 105 * 106 * @param inputStream The InputStream (will be fully read at this time) 107 */ 108 public ProtoBufParser(InputStream inputStream) { 109 if (inputStream.markSupported()) { 110 this.inputStream = inputStream; 111 } else { 112 this.inputStream = new BufferedInputStream(inputStream); 113 } 114 } 115 116 /** 117 * Read all records 118 * 119 * @return A collection of all records 120 * @throws IOException - if an IO error occurs 121 */ 122 public Collection<ProtoBufRecord> allRecords() throws IOException { 123 Collection<ProtoBufRecord> records = new ArrayList<>(); 124 while (this.hasNext()) { 125 records.add(new ProtoBufRecord(this)); 126 } 127 return records; 128 } 129 130 @Override 131 public void close() { 132 try { 133 this.inputStream.close(); 134 } catch (IOException e) { 135 Logging.error(e); 136 } 137 } 138 139 /** 140 * Check if there is more data to read 141 * 142 * @return {@code true} if there is more data to read 143 * @throws IOException - if an IO error occurs 144 */ 145 public boolean hasNext() throws IOException { 146 return this.inputStream.available() > 0; 147 } 148 149 /** 150 * Get the "next" WireType 151 * 152 * @return {@link WireType} expected 153 * @throws IOException - if an IO error occurs 154 */ 155 public WireType next() throws IOException { 156 this.inputStream.mark(16); 157 try { 158 return WireType.values()[this.inputStream.read() << 3]; 159 } finally { 160 this.inputStream.reset(); 161 } 162 } 163 164 /** 165 * Get the next byte 166 * 167 * @return The next byte 168 * @throws IOException - if an IO error occurs 169 */ 170 public int nextByte() throws IOException { 171 return this.inputStream.read(); 172 } 173 174 /** 175 * Get the next 32 bits ({@link WireType#THIRTY_TWO_BIT}) 176 * 177 * @return a byte array of the next 32 bits (4 bytes) 178 * @throws IOException - if an IO error occurs 179 */ 180 public byte[] nextFixed32() throws IOException { 181 // 4 bytes == 32 bits 182 return readNextBytes(4); 183 } 184 185 /** 186 * Get the next 64 bits ({@link WireType#SIXTY_FOUR_BIT}) 187 * 188 * @return a byte array of the next 64 bits (8 bytes) 189 * @throws IOException - if an IO error occurs 190 */ 191 public byte[] nextFixed64() throws IOException { 192 // 8 bytes == 64 bits 193 return readNextBytes(8); 194 } 195 196 /** 197 * Get the next delimited message ({@link WireType#LENGTH_DELIMITED}) 198 * 199 * @return The next length delimited message 200 * @throws IOException - if an IO error occurs 201 */ 202 public byte[] nextLengthDelimited() throws IOException { 203 int length = convertByteArray(this.nextVarInt(), VAR_INT_BYTE_SIZE).intValue(); 204 return readNextBytes(length); 205 } 206 207 /** 208 * Get the next var int ({@code WireType#VARINT}) 209 * 210 * @return The next var int ({@code int32}, {@code int64}, {@code uint32}, {@code uint64}, {@code bool}, {@code enum}) 211 * @throws IOException - if an IO error occurs 212 */ 213 public byte[] nextVarInt() throws IOException { 214 List<Byte> byteList = new ArrayList<>(); 215 int currentByte = this.nextByte(); 216 while ((byte) (currentByte & MOST_SIGNIFICANT_BYTE) == MOST_SIGNIFICANT_BYTE && currentByte > 0) { 217 // Get rid of the leading bit (shift left 1, then shift right 1 unsigned) 218 byteList.add((byte) (currentByte ^ MOST_SIGNIFICANT_BYTE)); 219 currentByte = this.nextByte(); 220 } 221 // The last byte doesn't drop the most significant bit 222 byteList.add((byte) currentByte); 223 byte[] byteArray = new byte[byteList.size()]; 224 for (int i = 0; i < byteList.size(); i++) { 225 byteArray[i] = byteList.get(i); 226 } 227 228 return byteArray; 229 } 230 231 /** 232 * Read an arbitrary number of bytes 233 * 234 * @param size The number of bytes to read 235 * @return a byte array of the specified size, filled with bytes read (unsigned) 236 * @throws IOException - if an IO error occurs 237 */ 238 private byte[] readNextBytes(int size) throws IOException { 239 byte[] bytesRead = new byte[size]; 240 for (int i = 0; i < bytesRead.length; i++) { 241 bytesRead[i] = (byte) this.nextByte(); 242 } 243 return bytesRead; 244 } 245 } -
src/org/openstreetmap/josm/data/protobuf/ProtoBufRecord.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import java.io.IOException; 5 import java.nio.charset.StandardCharsets; 6 import java.util.stream.Stream; 7 8 import org.openstreetmap.josm.tools.Utils; 9 10 /** 11 * A protobuf record, storing the {@link WireType}, the parsed field number, and the bytes for it. 12 * 13 * @author Taylor Smock 14 * @since xxx 15 */ 16 public class ProtoBufRecord implements AutoCloseable { 17 private static final byte[] EMPTY_BYTES = {}; 18 private final WireType type; 19 private final int field; 20 private byte[] bytes; 21 22 /** 23 * Create a new Protobuf record 24 * 25 * @param parser The parser to use to create the record 26 * @throws IOException - if an IO error occurs 27 */ 28 public ProtoBufRecord(ProtoBufParser parser) throws IOException { 29 Number number = ProtoBufParser.convertByteArray(parser.nextVarInt(), ProtoBufParser.VAR_INT_BYTE_SIZE); 30 // I don't foresee having field numbers > {@code Integer#MAX_VALUE >> 3} 31 this.field = (int) number.longValue() >> 3; 32 // 7 is 111 (so last three bits) 33 byte wireType = (byte) (number.longValue() & 7); 34 this.type = Stream.of(WireType.values()).filter(wType -> wType.getTypeRepresentation() == wireType).findFirst() 35 .orElse(WireType.UNKNOWN); 36 37 if (this.type == WireType.VARINT) { 38 this.bytes = parser.nextVarInt(); 39 } else if (this.type == WireType.SIXTY_FOUR_BIT) { 40 this.bytes = parser.nextFixed64(); 41 } else if (this.type == WireType.THIRTY_TWO_BIT) { 42 this.bytes = parser.nextFixed32(); 43 } else if (this.type == WireType.LENGTH_DELIMITED) { 44 this.bytes = parser.nextLengthDelimited(); 45 } else { 46 this.bytes = EMPTY_BYTES; 47 } 48 } 49 50 /** 51 * Get as a double ({@link WireType#SIXTY_FOUR_BIT}) 52 * 53 * @return the double 54 */ 55 public double asDouble() { 56 long doubleNumber = ProtoBufParser.convertByteArray(asFixed64(), ProtoBufParser.BYTE_SIZE).longValue(); 57 return Double.longBitsToDouble(doubleNumber); 58 } 59 60 /** 61 * Get as 32 bits ({@link WireType#THIRTY_TWO_BIT}) 62 * 63 * @return a byte array of the 32 bits (4 bytes) 64 */ 65 public byte[] asFixed32() { 66 // TODO verify, or just assume? 67 // 4 bytes == 32 bits 68 return this.bytes; 69 } 70 71 /** 72 * Get as 64 bits ({@link WireType#SIXTY_FOUR_BIT}) 73 * 74 * @return a byte array of the 64 bits (8 bytes) 75 */ 76 public byte[] asFixed64() { 77 // TODO verify, or just assume? 78 // 8 bytes == 64 bits 79 return this.bytes; 80 } 81 82 /** 83 * Get as a float ({@link WireType#THIRTY_TWO_BIT}) 84 * 85 * @return the float 86 */ 87 public float asFloat() { 88 int floatNumber = ProtoBufParser.convertByteArray(asFixed32(), ProtoBufParser.BYTE_SIZE).intValue(); 89 return Float.intBitsToFloat(floatNumber); 90 } 91 92 /** 93 * Get the signed var int ({@code WireType#VARINT}). 94 * These are specially encoded so that they take up less space. 95 * 96 * @return The signed var int ({@code sint32} or {@code sint64}) 97 */ 98 public Number asSignedVarInt() { 99 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 } -
src/org/openstreetmap/josm/data/protobuf/WireType.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 /** 5 * The WireTypes 6 * 7 * @author Taylor Smock 8 * @since xxx 9 */ 10 public enum WireType { 11 /** 12 * int32, int64, uint32, uint64, sing32, sint64, bool, enum 13 */ 14 VARINT(0), 15 /** 16 * fixed64, sfixed64, double 17 */ 18 SIXTY_FOUR_BIT(1), 19 /** 20 * string, bytes, embedded messages, packed repeated fields 21 */ 22 LENGTH_DELIMITED(2), 23 /** 24 * start groups 25 * 26 * @deprecated Unknown reason. Deprecated since at least 2012. 27 */ 28 @Deprecated 29 START_GROUP(3), 30 /** 31 * end groups 32 * 33 * @deprecated Unknown reason. Deprecated since at least 2012. 34 */ 35 @Deprecated 36 END_GROUP(4), 37 /** 38 * fixed32, sfixed32, float 39 */ 40 THIRTY_TWO_BIT(5), 41 42 /** 43 * For unknown WireTypes 44 */ 45 UNKNOWN(Byte.MAX_VALUE); 46 47 private final byte type; 48 49 WireType(int value) { 50 this.type = (byte) value; 51 } 52 53 /** 54 * Get the type representation (byte form) 55 * 56 * @return The wire type byte representation 57 */ 58 public byte getTypeRepresentation() { 59 return this.type; 60 } 61 } -
src/org/openstreetmap/josm/data/protobuf/ProtoBufPacked.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import java.util.ArrayList; 5 import java.util.List; 6 7 /** 8 * Parse packed values (only numerical values) 9 * 10 * @author Taylor Smock 11 * @since xxx 12 */ 13 public class ProtoBufPacked { 14 private final byte[] bytes; 15 private final Number[] numbers; 16 private int location; 17 18 /** 19 * Create a new ProtoBufPacked object 20 * 21 * @param bytes The packed bytes 22 */ 23 public ProtoBufPacked(byte[] bytes) { 24 this.location = 0; 25 this.bytes = bytes; 26 List<Number> numbersT = new ArrayList<>(); 27 while (this.location < bytes.length) { 28 numbersT.add(ProtoBufParser.convertByteArray(this.nextVarInt(), ProtoBufParser.VAR_INT_BYTE_SIZE)); 29 } 30 31 this.numbers = new Number[numbersT.size()]; 32 for (int i = 0; i < numbersT.size(); i++) { 33 this.numbers[i] = numbersT.get(i); 34 } 35 } 36 37 /** 38 * Get the parsed number array 39 * 40 * @return The number array 41 */ 42 public Number[] getArray() { 43 return this.numbers; 44 } 45 46 private byte[] nextVarInt() { 47 List<Byte> byteList = new ArrayList<>(); 48 while ((this.bytes[this.location] & ProtoBufParser.MOST_SIGNIFICANT_BYTE) 49 == ProtoBufParser.MOST_SIGNIFICANT_BYTE) { 50 // Get rid of the leading bit (shift left 1, then shift right 1 unsigned) 51 byteList.add((byte) (this.bytes[this.location++] ^ ProtoBufParser.MOST_SIGNIFICANT_BYTE)); 52 } 53 // The last byte doesn't drop the most significant bit 54 byteList.add(this.bytes[this.location++]); 55 byte[] byteArray = new byte[byteList.size()]; 56 for (int i = 0; i < byteList.size(); i++) { 57 byteArray[i] = byteList.get(i); 58 } 59 60 return byteArray; 61 } 62 } -
src/org/openstreetmap/josm/data/protobuf/ProtoBufParser.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import java.io.BufferedInputStream; 5 import java.io.ByteArrayInputStream; 6 import java.io.IOException; 7 import java.io.InputStream; 8 import java.util.ArrayList; 9 import java.util.Collection; 10 import java.util.List; 11 12 import org.openstreetmap.josm.tools.Logging; 13 14 /** 15 * A basic Protobuf parser 16 * 17 * @author Taylor Smock 18 * @since xxx 19 */ 20 public class ProtoBufParser implements AutoCloseable { 21 /** 22 * The default byte size (see {@link #VAR_INT_BYTE_SIZE} for var ints) 23 */ 24 public static final byte BYTE_SIZE = 8; 25 /** 26 * The byte size for var ints (since the first byte is just an indicator for if the var int is done) 27 */ 28 public static final byte VAR_INT_BYTE_SIZE = BYTE_SIZE - 1; 29 /** 30 * Used to get the most significant byte 31 */ 32 static final byte MOST_SIGNIFICANT_BYTE = (byte) (1 << 7); 33 /** 34 * Convert a byte array to a number (little endian) 35 * 36 * @param bytes The bytes to convert 37 * @param byteSize The size of the byte. For var ints, this is 7, for other ints, this is 8. 38 * @return An appropriate {@link Number} class. 39 */ 40 public static Number convertByteArray(byte[] bytes, byte byteSize) { 41 long number = 0; 42 for (int i = 0; i < bytes.length; i++) { 43 // Need to convert to uint64 in order to avoid bit operation from filling in 1's and overflow issues 44 number += Byte.toUnsignedLong(bytes[i]) << (byteSize * i); 45 } 46 return convertLong(number); 47 } 48 49 /** 50 * Convert a long to an appropriate {@link Number} class 51 * 52 * @param number The long to convert 53 * @return A {@link Number} 54 */ 55 public static Number convertLong(long number) { 56 // TODO deal with booleans 57 if (number <= Byte.MAX_VALUE && number >= Byte.MIN_VALUE) { 58 return (byte) number; 59 } else if (number <= Short.MAX_VALUE && number >= Short.MIN_VALUE) { 60 return (short) number; 61 } else if (number <= Integer.MAX_VALUE && number >= Integer.MIN_VALUE) { 62 return (int) number; 63 } 64 return number; 65 } 66 67 /** 68 * Decode a zig-zag encoded value 69 * 70 * @param signed The value to decode 71 * @return The decoded value 72 */ 73 public static Number decodeZigZag(Number signed) { 74 final long value = signed.longValue(); 75 return convertLong((value >> 1) ^ -(value & 1)); 76 } 77 78 /** 79 * Encode a number to a zig-zag encode value 80 * 81 * @param signed The number to encode 82 * @return The encoded value 83 */ 84 public static Number encodeZigZag(Number signed) { 85 final long value = signed.longValue(); 86 // This boundary condition could be >= or <= or both. Tests indicate that it doesn't actually matter. 87 // The only difference would be the number type returned, except it is always converted to the most basic type. 88 final int shift = (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE ? Long.BYTES : Integer.BYTES) * 8 - 1; 89 return convertLong((value << 1) ^ (value >> shift)); 90 } 91 92 private final InputStream inputStream; 93 94 /** 95 * Create a new parser 96 * 97 * @param bytes The bytes to parse 98 */ 99 public ProtoBufParser(byte[] bytes) { 100 this(new ByteArrayInputStream(bytes)); 101 } 102 103 /** 104 * Create a new parser 105 * 106 * @param inputStream The InputStream (will be fully read at this time) 107 */ 108 public ProtoBufParser(InputStream inputStream) { 109 if (inputStream.markSupported()) { 110 this.inputStream = inputStream; 111 } else { 112 this.inputStream = new BufferedInputStream(inputStream); 113 } 114 } 115 116 /** 117 * Read all records 118 * 119 * @return A collection of all records 120 * @throws IOException - if an IO error occurs 121 */ 122 public Collection<ProtoBufRecord> allRecords() throws IOException { 123 Collection<ProtoBufRecord> records = new ArrayList<>(); 124 while (this.hasNext()) { 125 records.add(new ProtoBufRecord(this)); 126 } 127 return records; 128 } 129 130 @Override 131 public void close() { 132 try { 133 this.inputStream.close(); 134 } catch (IOException e) { 135 Logging.error(e); 136 } 137 } 138 139 /** 140 * Check if there is more data to read 141 * 142 * @return {@code true} if there is more data to read 143 * @throws IOException - if an IO error occurs 144 */ 145 public boolean hasNext() throws IOException { 146 return this.inputStream.available() > 0; 147 } 148 149 /** 150 * Get the "next" WireType 151 * 152 * @return {@link WireType} expected 153 * @throws IOException - if an IO error occurs 154 */ 155 public WireType next() throws IOException { 156 this.inputStream.mark(16); 157 try { 158 return WireType.values()[this.inputStream.read() << 3]; 159 } finally { 160 this.inputStream.reset(); 161 } 162 } 163 164 /** 165 * Get the next byte 166 * 167 * @return The next byte 168 * @throws IOException - if an IO error occurs 169 */ 170 public int nextByte() throws IOException { 171 return this.inputStream.read(); 172 } 173 174 /** 175 * Get the next 32 bits ({@link WireType#THIRTY_TWO_BIT}) 176 * 177 * @return a byte array of the next 32 bits (4 bytes) 178 * @throws IOException - if an IO error occurs 179 */ 180 public byte[] nextFixed32() throws IOException { 181 // 4 bytes == 32 bits 182 return readNextBytes(4); 183 } 184 185 /** 186 * Get the next 64 bits ({@link WireType#SIXTY_FOUR_BIT}) 187 * 188 * @return a byte array of the next 64 bits (8 bytes) 189 * @throws IOException - if an IO error occurs 190 */ 191 public byte[] nextFixed64() throws IOException { 192 // 8 bytes == 64 bits 193 return readNextBytes(8); 194 } 195 196 /** 197 * Get the next delimited message ({@link WireType#LENGTH_DELIMITED}) 198 * 199 * @return The next length delimited message 200 * @throws IOException - if an IO error occurs 201 */ 202 public byte[] nextLengthDelimited() throws IOException { 203 int length = convertByteArray(this.nextVarInt(), VAR_INT_BYTE_SIZE).intValue(); 204 return readNextBytes(length); 205 } 206 207 /** 208 * Get the next var int ({@code WireType#VARINT}) 209 * 210 * @return The next var int ({@code int32}, {@code int64}, {@code uint32}, {@code uint64}, {@code bool}, {@code enum}) 211 * @throws IOException - if an IO error occurs 212 */ 213 public byte[] nextVarInt() throws IOException { 214 List<Byte> byteList = new ArrayList<>(); 215 int currentByte = this.nextByte(); 216 while ((byte) (currentByte & MOST_SIGNIFICANT_BYTE) == MOST_SIGNIFICANT_BYTE && currentByte > 0) { 217 // Get rid of the leading bit (shift left 1, then shift right 1 unsigned) 218 byteList.add((byte) (currentByte ^ MOST_SIGNIFICANT_BYTE)); 219 currentByte = this.nextByte(); 220 } 221 // The last byte doesn't drop the most significant bit 222 byteList.add((byte) currentByte); 223 byte[] byteArray = new byte[byteList.size()]; 224 for (int i = 0; i < byteList.size(); i++) { 225 byteArray[i] = byteList.get(i); 226 } 227 228 return byteArray; 229 } 230 231 /** 232 * Read an arbitrary number of bytes 233 * 234 * @param size The number of bytes to read 235 * @return a byte array of the specified size, filled with bytes read (unsigned) 236 * @throws IOException - if an IO error occurs 237 */ 238 private byte[] readNextBytes(int size) throws IOException { 239 byte[] bytesRead = new byte[size]; 240 for (int i = 0; i < bytesRead.length; i++) { 241 bytesRead[i] = (byte) this.nextByte(); 242 } 243 return bytesRead; 244 } 245 } -
src/org/openstreetmap/josm/data/protobuf/ProtoBufRecord.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import java.io.IOException; 5 import java.nio.charset.StandardCharsets; 6 import java.util.stream.Stream; 7 8 import org.openstreetmap.josm.tools.Utils; 9 10 /** 11 * A protobuf record, storing the {@link WireType}, the parsed field number, and the bytes for it. 12 * 13 * @author Taylor Smock 14 * @since xxx 15 */ 16 public class ProtoBufRecord implements AutoCloseable { 17 private static final byte[] EMPTY_BYTES = {}; 18 private final WireType type; 19 private final int field; 20 private byte[] bytes; 21 22 /** 23 * Create a new Protobuf record 24 * 25 * @param parser The parser to use to create the record 26 * @throws IOException - if an IO error occurs 27 */ 28 public ProtoBufRecord(ProtoBufParser parser) throws IOException { 29 Number number = ProtoBufParser.convertByteArray(parser.nextVarInt(), ProtoBufParser.VAR_INT_BYTE_SIZE); 30 // I don't foresee having field numbers > {@code Integer#MAX_VALUE >> 3} 31 this.field = (int) number.longValue() >> 3; 32 // 7 is 111 (so last three bits) 33 byte wireType = (byte) (number.longValue() & 7); 34 this.type = Stream.of(WireType.values()).filter(wType -> wType.getTypeRepresentation() == wireType).findFirst() 35 .orElse(WireType.UNKNOWN); 36 37 if (this.type == WireType.VARINT) { 38 this.bytes = parser.nextVarInt(); 39 } else if (this.type == WireType.SIXTY_FOUR_BIT) { 40 this.bytes = parser.nextFixed64(); 41 } else if (this.type == WireType.THIRTY_TWO_BIT) { 42 this.bytes = parser.nextFixed32(); 43 } else if (this.type == WireType.LENGTH_DELIMITED) { 44 this.bytes = parser.nextLengthDelimited(); 45 } else { 46 this.bytes = EMPTY_BYTES; 47 } 48 } 49 50 /** 51 * Get as a double ({@link WireType#SIXTY_FOUR_BIT}) 52 * 53 * @return the double 54 */ 55 public double asDouble() { 56 long doubleNumber = ProtoBufParser.convertByteArray(asFixed64(), ProtoBufParser.BYTE_SIZE).longValue(); 57 return Double.longBitsToDouble(doubleNumber); 58 } 59 60 /** 61 * Get as 32 bits ({@link WireType#THIRTY_TWO_BIT}) 62 * 63 * @return a byte array of the 32 bits (4 bytes) 64 */ 65 public byte[] asFixed32() { 66 // TODO verify, or just assume? 67 // 4 bytes == 32 bits 68 return this.bytes; 69 } 70 71 /** 72 * Get as 64 bits ({@link WireType#SIXTY_FOUR_BIT}) 73 * 74 * @return a byte array of the 64 bits (8 bytes) 75 */ 76 public byte[] asFixed64() { 77 // TODO verify, or just assume? 78 // 8 bytes == 64 bits 79 return this.bytes; 80 } 81 82 /** 83 * Get as a float ({@link WireType#THIRTY_TWO_BIT}) 84 * 85 * @return the float 86 */ 87 public float asFloat() { 88 int floatNumber = ProtoBufParser.convertByteArray(asFixed32(), ProtoBufParser.BYTE_SIZE).intValue(); 89 return Float.intBitsToFloat(floatNumber); 90 } 91 92 /** 93 * Get the signed var int ({@code WireType#VARINT}). 94 * These are specially encoded so that they take up less space. 95 * 96 * @return The signed var int ({@code sint32} or {@code sint64}) 97 */ 98 public Number asSignedVarInt() { 99 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 } -
src/org/openstreetmap/josm/data/protobuf/WireType.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 /** 5 * The WireTypes 6 * 7 * @author Taylor Smock 8 * @since xxx 9 */ 10 public enum WireType { 11 /** 12 * int32, int64, uint32, uint64, sing32, sint64, bool, enum 13 */ 14 VARINT(0), 15 /** 16 * fixed64, sfixed64, double 17 */ 18 SIXTY_FOUR_BIT(1), 19 /** 20 * string, bytes, embedded messages, packed repeated fields 21 */ 22 LENGTH_DELIMITED(2), 23 /** 24 * start groups 25 * 26 * @deprecated Unknown reason. Deprecated since at least 2012. 27 */ 28 @Deprecated 29 START_GROUP(3), 30 /** 31 * end groups 32 * 33 * @deprecated Unknown reason. Deprecated since at least 2012. 34 */ 35 @Deprecated 36 END_GROUP(4), 37 /** 38 * fixed32, sfixed32, float 39 */ 40 THIRTY_TWO_BIT(5), 41 42 /** 43 * For unknown WireTypes 44 */ 45 UNKNOWN(Byte.MAX_VALUE); 46 47 private final byte type; 48 49 WireType(int value) { 50 this.type = (byte) value; 51 } 52 53 /** 54 * Get the type representation (byte form) 55 * 56 * @return The wire type byte representation 57 */ 58 public byte getTypeRepresentation() { 59 return this.type; 60 } 61 } -
test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufParserTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 6 import org.junit.jupiter.api.Test; 7 8 /** 9 * Test class for {@link ProtoBufParser} 10 * @author Taylor Smock 11 * @since xxx 12 */ 13 class ProtoBufParserTest { 14 /** 15 * Check that we are appropriately converting values to the "smallest" type 16 */ 17 @Test 18 void testConvertLong() { 19 // No casting due to auto conversions 20 assertEquals(Byte.MAX_VALUE, ProtoBufParser.convertLong(Byte.MAX_VALUE)); 21 assertEquals(Byte.MIN_VALUE, ProtoBufParser.convertLong(Byte.MIN_VALUE)); 22 assertEquals(Short.MIN_VALUE, ProtoBufParser.convertLong(Short.MIN_VALUE)); 23 assertEquals(Short.MAX_VALUE, ProtoBufParser.convertLong(Short.MAX_VALUE)); 24 assertEquals(Integer.MAX_VALUE, ProtoBufParser.convertLong(Integer.MAX_VALUE)); 25 assertEquals(Integer.MIN_VALUE, ProtoBufParser.convertLong(Integer.MIN_VALUE)); 26 assertEquals(Long.MIN_VALUE, ProtoBufParser.convertLong(Long.MIN_VALUE)); 27 assertEquals(Long.MAX_VALUE, ProtoBufParser.convertLong(Long.MAX_VALUE)); 28 } 29 30 /** 31 * Check that zig zags are appropriately encoded. 32 */ 33 @Test 34 void testEncodeZigZag() { 35 assertEquals(0, ProtoBufParser.encodeZigZag(0).byteValue()); 36 assertEquals(1, ProtoBufParser.encodeZigZag(-1).byteValue()); 37 assertEquals(2, ProtoBufParser.encodeZigZag(1).byteValue()); 38 assertEquals(3, ProtoBufParser.encodeZigZag(-2).byteValue()); 39 assertEquals(254, ProtoBufParser.encodeZigZag(Byte.MAX_VALUE).shortValue()); 40 assertEquals(255, ProtoBufParser.encodeZigZag(Byte.MIN_VALUE).shortValue()); 41 assertEquals(65_534, ProtoBufParser.encodeZigZag(Short.MAX_VALUE).intValue()); 42 assertEquals(65_535, ProtoBufParser.encodeZigZag(Short.MIN_VALUE).intValue()); 43 // These integers check a possible boundary condition (the boundary between using the 32/64 bit encoding methods) 44 assertEquals(4_294_967_292L, ProtoBufParser.encodeZigZag(Integer.MAX_VALUE - 1).longValue()); 45 assertEquals(4_294_967_293L, ProtoBufParser.encodeZigZag(Integer.MIN_VALUE + 1).longValue()); 46 assertEquals(4_294_967_294L, ProtoBufParser.encodeZigZag(Integer.MAX_VALUE).longValue()); 47 assertEquals(4_294_967_295L, ProtoBufParser.encodeZigZag(Integer.MIN_VALUE).longValue()); 48 assertEquals(4_294_967_296L, ProtoBufParser.encodeZigZag(Integer.MAX_VALUE + 1L).longValue()); 49 assertEquals(4_294_967_297L, ProtoBufParser.encodeZigZag(Integer.MIN_VALUE - 1L).longValue()); 50 } 51 } -
test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufRecordTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 6 7 import java.io.IOException; 8 9 import org.junit.jupiter.api.Test; 10 11 /** 12 * Test class for specific {@link ProtoBufRecord} functionality 13 */ 14 class ProtoBufRecordTest { 15 @Test 16 void testFixed32() throws IOException { 17 ProtoBufParser parser = new ProtoBufParser(ProtoBufTest.toByteArray(new int[] {0x0d, 0x00, 0x00, 0x80, 0x3f})); 18 ProtoBufRecord thirtyTwoBit = new ProtoBufRecord(parser); 19 assertEquals(WireType.THIRTY_TWO_BIT, thirtyTwoBit.getType()); 20 assertEquals(1f, thirtyTwoBit.asFloat()); 21 } 22 23 @Test 24 void testUnknown() throws IOException { 25 ProtoBufParser parser = new ProtoBufParser(ProtoBufTest.toByteArray(new int[] {0x0f, 0x00, 0x00, 0x80, 0x3f})); 26 ProtoBufRecord unknown = new ProtoBufRecord(parser); 27 assertEquals(WireType.UNKNOWN, unknown.getType()); 28 assertEquals(0, unknown.getBytes().length); 29 } 30 } -
test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertNotNull; 6 import static org.junit.jupiter.api.Assertions.fail; 7 8 import java.awt.geom.Ellipse2D; 9 import java.io.ByteArrayInputStream; 10 import java.io.File; 11 import java.io.IOException; 12 import java.io.InputStream; 13 import java.nio.file.Paths; 14 import java.text.MessageFormat; 15 import java.util.ArrayList; 16 import java.util.Collection; 17 import java.util.List; 18 import java.util.stream.Collectors; 19 20 import org.openstreetmap.josm.TestUtils; 21 import org.openstreetmap.josm.data.coor.LatLon; 22 import org.openstreetmap.josm.data.imagery.ImageryInfo; 23 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.Feature; 24 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.Layer; 25 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MVTTile; 26 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MapboxVectorTileSource; 27 import org.openstreetmap.josm.data.osm.BBox; 28 import org.openstreetmap.josm.data.osm.Node; 29 import org.openstreetmap.josm.data.osm.Way; 30 import org.openstreetmap.josm.data.vector.VectorDataSet; 31 import org.openstreetmap.josm.data.vector.VectorNode; 32 import org.openstreetmap.josm.data.vector.VectorWay; 33 import org.openstreetmap.josm.io.Compression; 34 import org.openstreetmap.josm.testutils.JOSMTestRules; 35 36 import org.junit.jupiter.api.Test; 37 import org.junit.jupiter.api.extension.RegisterExtension; 38 39 /** 40 * Test class for {@link ProtoBufParser} and {@link ProtoBufRecord} 41 * 42 * @author Taylor Smock 43 * @since xxx 44 */ 45 class ProtoBufTest { 46 /** 47 * Convert an int array into a byte array 48 * @param intArray The int array to convert (NOTE: numbers must be below 255) 49 * @return A byte array that can be used 50 */ 51 static byte[] toByteArray(int[] intArray) { 52 byte[] byteArray = new byte[intArray.length]; 53 for (int i = 0; i < intArray.length; i++) { 54 if (intArray[i] > Byte.MAX_VALUE - Byte.MIN_VALUE) { 55 throw new IllegalArgumentException(); 56 } 57 byteArray[i] = Integer.valueOf(intArray[i]).byteValue(); 58 } 59 return byteArray; 60 } 61 62 @RegisterExtension 63 JOSMTestRules josmTestRules = new JOSMTestRules().preferences(); 64 65 private Number bytesToVarInt(int... bytes) { 66 byte[] byteArray = new byte[bytes.length]; 67 for (int i = 0; i < bytes.length; i++) { 68 byteArray[i] = (byte) bytes[i]; 69 } 70 return ProtoBufParser.convertByteArray(byteArray, ProtoBufParser.VAR_INT_BYTE_SIZE); 71 } 72 73 /** 74 * Test reading tile from Mapillary ( 14/3251/6258 ) 75 * 76 * @throws IOException if there is a problem reading the file 77 */ 78 @Test 79 void testRead_14_3251_6258() throws IOException { 80 File vectorTile = Paths.get(TestUtils.getTestDataRoot(), "pbf", "mapillary", "14", "3251", "6258.mvt").toFile(); 81 InputStream inputStream = Compression.getUncompressedFileInputStream(vectorTile); 82 Collection<ProtoBufRecord> records = new ProtoBufParser(inputStream).allRecords(); 83 assertEquals(2, records.size()); 84 List<Layer> layers = new ArrayList<>(); 85 for (ProtoBufRecord record : records) { 86 if (record.getField() == Layer.LAYER_FIELD) { 87 layers.add(new Layer(record.getBytes())); 88 } else { 89 fail(MessageFormat.format("Invalid field {0}", record.getField())); 90 } 91 } 92 Layer mapillarySequences = layers.get(0); 93 Layer mapillaryPictures = layers.get(1); 94 assertEquals("mapillary-sequences", mapillarySequences.getName()); 95 assertEquals("mapillary-images", mapillaryPictures.getName()); 96 assertEquals(2048, mapillarySequences.getExtent()); 97 assertEquals(2048, mapillaryPictures.getExtent()); 98 99 assertEquals(1, 100 mapillarySequences.getFeatures().stream().filter(feature -> feature.getId() == 241083111).count()); 101 Feature testSequence = mapillarySequences.getFeatures().stream().filter(feature -> feature.getId() == 241083111) 102 .findAny().orElse(null); 103 assertEquals("jgxkXqVFM4jepMG3vP5Q9A", testSequence.getTags().get("key")); 104 assertEquals("C15Ul6qVMfQFlzRcmQCLcA", testSequence.getTags().get("ikey")); 105 assertEquals("x0hTY8cakpy0m3ui1GaG1A", testSequence.getTags().get("userkey")); 106 assertEquals(Long.valueOf(1565196718638L), Long.valueOf(testSequence.getTags().get("captured_at"))); 107 assertEquals(0, Integer.parseInt(testSequence.getTags().get("pano"))); 108 } 109 110 @Test 111 void testRead_17_26028_50060() throws IOException { 112 File vectorTile = Paths.get(TestUtils.getTestDataRoot(), "pbf", "openinframap", "17", "26028", "50060.pbf") 113 .toFile(); 114 InputStream inputStream = Compression.getUncompressedFileInputStream(vectorTile); 115 Collection<ProtoBufRecord> records = new ProtoBufParser(inputStream).allRecords(); 116 List<Layer> layers = new ArrayList<>(); 117 for (ProtoBufRecord record : records) { 118 if (record.getField() == Layer.LAYER_FIELD) { 119 layers.add(new Layer(record.getBytes())); 120 } else { 121 fail(MessageFormat.format("Invalid field {0}", record.getField())); 122 } 123 } 124 assertEquals(19, layers.size()); 125 List<Layer> dataLayers = layers.stream().filter(layer -> !layer.getFeatures().isEmpty()) 126 .collect(Collectors.toList()); 127 // power_plant, power_plant_point, power_generator, power_heatmap_solar, and power_generator_area 128 assertEquals(5, dataLayers.size()); 129 130 // power_generator_area was rendered incorrectly 131 final Layer powerGeneratorArea = dataLayers.stream() 132 .filter(layer -> "power_generator_area".equals(layer.getName())).findAny().orElse(null); 133 assertNotNull(powerGeneratorArea); 134 final int extent = powerGeneratorArea.getExtent(); 135 // 17/26028/50060 bounds 136 VectorDataSet vectorDataSet = new VectorDataSet(); 137 MVTTile vectorTile1 = new MVTTile(new MapboxVectorTileSource(new ImageryInfo("Test info", "example.org")), 138 26028, 50060, 17); 139 vectorTile1.loadImage(Compression.getUncompressedFileInputStream(vectorTile)); 140 vectorDataSet.addTileData(vectorTile1); 141 vectorDataSet.setZoom(17); 142 final Way one = new Way(); 143 one.addNode(new Node(new LatLon(39.0687509, -108.5100816))); 144 one.addNode(new Node(new LatLon(39.0687509, -108.5095751))); 145 one.addNode(new Node(new LatLon(39.0687169, -108.5095751))); 146 one.addNode(new Node(new LatLon(39.0687169, -108.5100816))); 147 one.addNode(one.getNode(0)); 148 one.setOsmId(666293899, 2); 149 final BBox searchBBox = one.getBBox(); 150 searchBBox.addPrimitive(one, 0.00001); 151 final Collection<VectorNode> searchedNodes = vectorDataSet.searchNodes(searchBBox); 152 final Collection<VectorWay> searchedWays = vectorDataSet.searchWays(searchBBox); 153 assertEquals(4, searchedNodes.size()); 154 } 155 156 @Test 157 void testReadVarInt() { 158 assertEquals(ProtoBufParser.convertLong(0), bytesToVarInt(0x0)); 159 assertEquals(ProtoBufParser.convertLong(1), bytesToVarInt(0x1)); 160 assertEquals(ProtoBufParser.convertLong(127), bytesToVarInt(0x7f)); 161 // This should b 0xff 0xff 0xff 0xff 0x07, but we drop the leading bit when reading to a byte array 162 Number actual = bytesToVarInt(0x7f, 0x7f, 0x7f, 0x7f, 0x07); 163 assertEquals(ProtoBufParser.convertLong(Integer.MAX_VALUE), actual, 164 MessageFormat.format("Expected {0} but got {1}", Integer.toBinaryString(Integer.MAX_VALUE), 165 Long.toBinaryString(actual.longValue()))); 166 } 167 168 /** 169 * Test simple message. 170 * Check that a simple message is readable 171 * 172 * @throws IOException - if an IO error occurs 173 */ 174 @Test 175 void testSimpleMessage() throws IOException { 176 ProtoBufParser parser = new ProtoBufParser(new byte[] { (byte) 0x08, (byte) 0x96, (byte) 0x01 }); 177 ProtoBufRecord record = new ProtoBufRecord(parser); 178 assertEquals(WireType.VARINT, record.getType()); 179 assertEquals(150, record.asUnsignedVarInt().intValue()); 180 } 181 182 @Test 183 void testSingletonMultiPoint() throws IOException { 184 Collection<ProtoBufRecord> records = new ProtoBufParser(new ByteArrayInputStream(toByteArray( 185 new int[] { 0x1a, 0x2c, 0x78, 0x02, 0x0a, 0x03, 0x74, 0x6d, 0x70, 0x28, 0x80, 0x20, 0x1a, 0x04, 0x6e, 186 0x61, 0x6d, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x54, 0x65, 0x73, 0x74, 0x20, 0x6e, 0x61, 0x6d, 0x65, 187 0x12, 0x0d, 0x18, 0x01, 0x12, 0x02, 0x00, 0x00, 0x22, 0x05, 0x09, 0xe0, 0x3e, 0x84, 0x27 }))) 188 .allRecords(); 189 List<Layer> layers = new ArrayList<>(); 190 for (ProtoBufRecord record : records) { 191 if (record.getField() == Layer.LAYER_FIELD) { 192 layers.add(new Layer(record.getBytes())); 193 } else { 194 fail(MessageFormat.format("Invalid field {0}", record.getField())); 195 } 196 } 197 assertEquals(1, layers.size()); 198 assertEquals(1, layers.get(0).getGeometry().size()); 199 Ellipse2D shape = (Ellipse2D) layers.get(0).getGeometry().iterator().next().getShapes().iterator().next(); 200 assertEquals(4016, shape.getCenterX()); 201 assertEquals(2498, shape.getCenterY()); 202 } 203 204 @Test 205 void testZigZag() { 206 assertEquals(0, ProtoBufParser.decodeZigZag(0).intValue()); 207 assertEquals(-1, ProtoBufParser.decodeZigZag(1).intValue()); 208 assertEquals(1, ProtoBufParser.decodeZigZag(2).intValue()); 209 assertEquals(-2, ProtoBufParser.decodeZigZag(3).intValue()); 210 } 211 } -
test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertNotNull; 6 import static org.junit.jupiter.api.Assertions.fail; 7 8 import java.awt.geom.Ellipse2D; 9 import java.io.ByteArrayInputStream; 10 import java.io.File; 11 import java.io.IOException; 12 import java.io.InputStream; 13 import java.nio.file.Paths; 14 import java.text.MessageFormat; 15 import java.util.ArrayList; 16 import java.util.Collection; 17 import java.util.List; 18 import java.util.stream.Collectors; 19 20 import org.openstreetmap.josm.TestUtils; 21 import org.openstreetmap.josm.data.coor.LatLon; 22 import org.openstreetmap.josm.data.imagery.ImageryInfo; 23 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.Feature; 24 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.Layer; 25 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MVTTile; 26 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MapboxVectorTileSource; 27 import org.openstreetmap.josm.data.osm.BBox; 28 import org.openstreetmap.josm.data.osm.Node; 29 import org.openstreetmap.josm.data.osm.Way; 30 import org.openstreetmap.josm.data.vector.VectorDataSet; 31 import org.openstreetmap.josm.data.vector.VectorNode; 32 import org.openstreetmap.josm.data.vector.VectorWay; 33 import org.openstreetmap.josm.io.Compression; 34 import org.openstreetmap.josm.testutils.JOSMTestRules; 35 36 import org.junit.jupiter.api.Test; 37 import org.junit.jupiter.api.extension.RegisterExtension; 38 39 /** 40 * Test class for {@link ProtoBufParser} and {@link ProtoBufRecord} 41 * 42 * @author Taylor Smock 43 * @since xxx 44 */ 45 class ProtoBufTest { 46 /** 47 * Convert an int array into a byte array 48 * @param intArray The int array to convert (NOTE: numbers must be below 255) 49 * @return A byte array that can be used 50 */ 51 static byte[] toByteArray(int[] intArray) { 52 byte[] byteArray = new byte[intArray.length]; 53 for (int i = 0; i < intArray.length; i++) { 54 if (intArray[i] > Byte.MAX_VALUE - Byte.MIN_VALUE) { 55 throw new IllegalArgumentException(); 56 } 57 byteArray[i] = Integer.valueOf(intArray[i]).byteValue(); 58 } 59 return byteArray; 60 } 61 62 @RegisterExtension 63 JOSMTestRules josmTestRules = new JOSMTestRules().preferences(); 64 65 private Number bytesToVarInt(int... bytes) { 66 byte[] byteArray = new byte[bytes.length]; 67 for (int i = 0; i < bytes.length; i++) { 68 byteArray[i] = (byte) bytes[i]; 69 } 70 return ProtoBufParser.convertByteArray(byteArray, ProtoBufParser.VAR_INT_BYTE_SIZE); 71 } 72 73 /** 74 * Test reading tile from Mapillary ( 14/3251/6258 ) 75 * 76 * @throws IOException if there is a problem reading the file 77 */ 78 @Test 79 void testRead_14_3251_6258() throws IOException { 80 File vectorTile = Paths.get(TestUtils.getTestDataRoot(), "pbf", "mapillary", "14", "3251", "6258.mvt").toFile(); 81 InputStream inputStream = Compression.getUncompressedFileInputStream(vectorTile); 82 Collection<ProtoBufRecord> records = new ProtoBufParser(inputStream).allRecords(); 83 assertEquals(2, records.size()); 84 List<Layer> layers = new ArrayList<>(); 85 for (ProtoBufRecord record : records) { 86 if (record.getField() == Layer.LAYER_FIELD) { 87 layers.add(new Layer(record.getBytes())); 88 } else { 89 fail(MessageFormat.format("Invalid field {0}", record.getField())); 90 } 91 } 92 Layer mapillarySequences = layers.get(0); 93 Layer mapillaryPictures = layers.get(1); 94 assertEquals("mapillary-sequences", mapillarySequences.getName()); 95 assertEquals("mapillary-images", mapillaryPictures.getName()); 96 assertEquals(2048, mapillarySequences.getExtent()); 97 assertEquals(2048, mapillaryPictures.getExtent()); 98 99 assertEquals(1, 100 mapillarySequences.getFeatures().stream().filter(feature -> feature.getId() == 241083111).count()); 101 Feature testSequence = mapillarySequences.getFeatures().stream().filter(feature -> feature.getId() == 241083111) 102 .findAny().orElse(null); 103 assertEquals("jgxkXqVFM4jepMG3vP5Q9A", testSequence.getTags().get("key")); 104 assertEquals("C15Ul6qVMfQFlzRcmQCLcA", testSequence.getTags().get("ikey")); 105 assertEquals("x0hTY8cakpy0m3ui1GaG1A", testSequence.getTags().get("userkey")); 106 assertEquals(Long.valueOf(1565196718638L), Long.valueOf(testSequence.getTags().get("captured_at"))); 107 assertEquals(0, Integer.parseInt(testSequence.getTags().get("pano"))); 108 } 109 110 @Test 111 void testRead_17_26028_50060() throws IOException { 112 File vectorTile = Paths.get(TestUtils.getTestDataRoot(), "pbf", "openinframap", "17", "26028", "50060.pbf") 113 .toFile(); 114 InputStream inputStream = Compression.getUncompressedFileInputStream(vectorTile); 115 Collection<ProtoBufRecord> records = new ProtoBufParser(inputStream).allRecords(); 116 List<Layer> layers = new ArrayList<>(); 117 for (ProtoBufRecord record : records) { 118 if (record.getField() == Layer.LAYER_FIELD) { 119 layers.add(new Layer(record.getBytes())); 120 } else { 121 fail(MessageFormat.format("Invalid field {0}", record.getField())); 122 } 123 } 124 assertEquals(19, layers.size()); 125 List<Layer> dataLayers = layers.stream().filter(layer -> !layer.getFeatures().isEmpty()) 126 .collect(Collectors.toList()); 127 // power_plant, power_plant_point, power_generator, power_heatmap_solar, and power_generator_area 128 assertEquals(5, dataLayers.size()); 129 130 // power_generator_area was rendered incorrectly 131 final Layer powerGeneratorArea = dataLayers.stream() 132 .filter(layer -> "power_generator_area".equals(layer.getName())).findAny().orElse(null); 133 assertNotNull(powerGeneratorArea); 134 final int extent = powerGeneratorArea.getExtent(); 135 // 17/26028/50060 bounds 136 VectorDataSet vectorDataSet = new VectorDataSet(); 137 MVTTile vectorTile1 = new MVTTile(new MapboxVectorTileSource(new ImageryInfo("Test info", "example.org")), 138 26028, 50060, 17); 139 vectorTile1.loadImage(Compression.getUncompressedFileInputStream(vectorTile)); 140 vectorDataSet.addTileData(vectorTile1); 141 vectorDataSet.setZoom(17); 142 final Way one = new Way(); 143 one.addNode(new Node(new LatLon(39.0687509, -108.5100816))); 144 one.addNode(new Node(new LatLon(39.0687509, -108.5095751))); 145 one.addNode(new Node(new LatLon(39.0687169, -108.5095751))); 146 one.addNode(new Node(new LatLon(39.0687169, -108.5100816))); 147 one.addNode(one.getNode(0)); 148 one.setOsmId(666293899, 2); 149 final BBox searchBBox = one.getBBox(); 150 searchBBox.addPrimitive(one, 0.00001); 151 final Collection<VectorNode> searchedNodes = vectorDataSet.searchNodes(searchBBox); 152 final Collection<VectorWay> searchedWays = vectorDataSet.searchWays(searchBBox); 153 assertEquals(4, searchedNodes.size()); 154 } 155 156 @Test 157 void testReadVarInt() { 158 assertEquals(ProtoBufParser.convertLong(0), bytesToVarInt(0x0)); 159 assertEquals(ProtoBufParser.convertLong(1), bytesToVarInt(0x1)); 160 assertEquals(ProtoBufParser.convertLong(127), bytesToVarInt(0x7f)); 161 // This should b 0xff 0xff 0xff 0xff 0x07, but we drop the leading bit when reading to a byte array 162 Number actual = bytesToVarInt(0x7f, 0x7f, 0x7f, 0x7f, 0x07); 163 assertEquals(ProtoBufParser.convertLong(Integer.MAX_VALUE), actual, 164 MessageFormat.format("Expected {0} but got {1}", Integer.toBinaryString(Integer.MAX_VALUE), 165 Long.toBinaryString(actual.longValue()))); 166 } 167 168 /** 169 * Test simple message. 170 * Check that a simple message is readable 171 * 172 * @throws IOException - if an IO error occurs 173 */ 174 @Test 175 void testSimpleMessage() throws IOException { 176 ProtoBufParser parser = new ProtoBufParser(new byte[] { (byte) 0x08, (byte) 0x96, (byte) 0x01 }); 177 ProtoBufRecord record = new ProtoBufRecord(parser); 178 assertEquals(WireType.VARINT, record.getType()); 179 assertEquals(150, record.asUnsignedVarInt().intValue()); 180 } 181 182 @Test 183 void testSingletonMultiPoint() throws IOException { 184 Collection<ProtoBufRecord> records = new ProtoBufParser(new ByteArrayInputStream(toByteArray( 185 new int[] { 0x1a, 0x2c, 0x78, 0x02, 0x0a, 0x03, 0x74, 0x6d, 0x70, 0x28, 0x80, 0x20, 0x1a, 0x04, 0x6e, 186 0x61, 0x6d, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x54, 0x65, 0x73, 0x74, 0x20, 0x6e, 0x61, 0x6d, 0x65, 187 0x12, 0x0d, 0x18, 0x01, 0x12, 0x02, 0x00, 0x00, 0x22, 0x05, 0x09, 0xe0, 0x3e, 0x84, 0x27 }))) 188 .allRecords(); 189 List<Layer> layers = new ArrayList<>(); 190 for (ProtoBufRecord record : records) { 191 if (record.getField() == Layer.LAYER_FIELD) { 192 layers.add(new Layer(record.getBytes())); 193 } else { 194 fail(MessageFormat.format("Invalid field {0}", record.getField())); 195 } 196 } 197 assertEquals(1, layers.size()); 198 assertEquals(1, layers.get(0).getGeometry().size()); 199 Ellipse2D shape = (Ellipse2D) layers.get(0).getGeometry().iterator().next().getShapes().iterator().next(); 200 assertEquals(4016, shape.getCenterX()); 201 assertEquals(2498, shape.getCenterY()); 202 } 203 204 @Test 205 void testZigZag() { 206 assertEquals(0, ProtoBufParser.decodeZigZag(0).intValue()); 207 assertEquals(-1, ProtoBufParser.decodeZigZag(1).intValue()); 208 assertEquals(1, ProtoBufParser.decodeZigZag(2).intValue()); 209 assertEquals(-2, ProtoBufParser.decodeZigZag(3).intValue()); 210 } 211 } -
test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufParserTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 6 import org.junit.jupiter.api.Test; 7 8 /** 9 * Test class for {@link ProtoBufParser} 10 * @author Taylor Smock 11 * @since xxx 12 */ 13 class ProtoBufParserTest { 14 /** 15 * Check that we are appropriately converting values to the "smallest" type 16 */ 17 @Test 18 void testConvertLong() { 19 // No casting due to auto conversions 20 assertEquals(Byte.MAX_VALUE, ProtoBufParser.convertLong(Byte.MAX_VALUE)); 21 assertEquals(Byte.MIN_VALUE, ProtoBufParser.convertLong(Byte.MIN_VALUE)); 22 assertEquals(Short.MIN_VALUE, ProtoBufParser.convertLong(Short.MIN_VALUE)); 23 assertEquals(Short.MAX_VALUE, ProtoBufParser.convertLong(Short.MAX_VALUE)); 24 assertEquals(Integer.MAX_VALUE, ProtoBufParser.convertLong(Integer.MAX_VALUE)); 25 assertEquals(Integer.MIN_VALUE, ProtoBufParser.convertLong(Integer.MIN_VALUE)); 26 assertEquals(Long.MIN_VALUE, ProtoBufParser.convertLong(Long.MIN_VALUE)); 27 assertEquals(Long.MAX_VALUE, ProtoBufParser.convertLong(Long.MAX_VALUE)); 28 } 29 30 /** 31 * Check that zig zags are appropriately encoded. 32 */ 33 @Test 34 void testEncodeZigZag() { 35 assertEquals(0, ProtoBufParser.encodeZigZag(0).byteValue()); 36 assertEquals(1, ProtoBufParser.encodeZigZag(-1).byteValue()); 37 assertEquals(2, ProtoBufParser.encodeZigZag(1).byteValue()); 38 assertEquals(3, ProtoBufParser.encodeZigZag(-2).byteValue()); 39 assertEquals(254, ProtoBufParser.encodeZigZag(Byte.MAX_VALUE).shortValue()); 40 assertEquals(255, ProtoBufParser.encodeZigZag(Byte.MIN_VALUE).shortValue()); 41 assertEquals(65_534, ProtoBufParser.encodeZigZag(Short.MAX_VALUE).intValue()); 42 assertEquals(65_535, ProtoBufParser.encodeZigZag(Short.MIN_VALUE).intValue()); 43 // These integers check a possible boundary condition (the boundary between using the 32/64 bit encoding methods) 44 assertEquals(4_294_967_292L, ProtoBufParser.encodeZigZag(Integer.MAX_VALUE - 1).longValue()); 45 assertEquals(4_294_967_293L, ProtoBufParser.encodeZigZag(Integer.MIN_VALUE + 1).longValue()); 46 assertEquals(4_294_967_294L, ProtoBufParser.encodeZigZag(Integer.MAX_VALUE).longValue()); 47 assertEquals(4_294_967_295L, ProtoBufParser.encodeZigZag(Integer.MIN_VALUE).longValue()); 48 assertEquals(4_294_967_296L, ProtoBufParser.encodeZigZag(Integer.MAX_VALUE + 1L).longValue()); 49 assertEquals(4_294_967_297L, ProtoBufParser.encodeZigZag(Integer.MIN_VALUE - 1L).longValue()); 50 } 51 } -
test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufRecordTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 6 7 import java.io.IOException; 8 9 import org.junit.jupiter.api.Test; 10 11 /** 12 * Test class for specific {@link ProtoBufRecord} functionality 13 */ 14 class ProtoBufRecordTest { 15 @Test 16 void testFixed32() throws IOException { 17 ProtoBufParser parser = new ProtoBufParser(ProtoBufTest.toByteArray(new int[] {0x0d, 0x00, 0x00, 0x80, 0x3f})); 18 ProtoBufRecord thirtyTwoBit = new ProtoBufRecord(parser); 19 assertEquals(WireType.THIRTY_TWO_BIT, thirtyTwoBit.getType()); 20 assertEquals(1f, thirtyTwoBit.asFloat()); 21 } 22 23 @Test 24 void testUnknown() throws IOException { 25 ProtoBufParser parser = new ProtoBufParser(ProtoBufTest.toByteArray(new int[] {0x0f, 0x00, 0x00, 0x80, 0x3f})); 26 ProtoBufRecord unknown = new ProtoBufRecord(parser); 27 assertEquals(WireType.UNKNOWN, unknown.getType()); 28 assertEquals(0, unknown.getBytes().length); 29 } 30 }