001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.util.struct; 006 007import java.nio.BufferUnderflowException; 008import java.nio.ByteBuffer; 009import java.nio.ByteOrder; 010import java.nio.ReadOnlyBufferException; 011import java.nio.charset.StandardCharsets; 012 013/** Dynamic (run-time) access to a serialized raw struct. */ 014public final class DynamicStruct { 015 private DynamicStruct(StructDescriptor desc, ByteBuffer data) { 016 m_desc = desc; 017 m_data = data.order(ByteOrder.LITTLE_ENDIAN); 018 } 019 020 /** 021 * Constructs a new dynamic struct object with internal storage. The descriptor must be valid. The 022 * internal storage is allocated using ByteBuffer.allocate(). 023 * 024 * @param desc struct descriptor 025 * @return dynamic struct object 026 * @throws IllegalStateException if struct descriptor is invalid 027 */ 028 public static DynamicStruct allocate(StructDescriptor desc) { 029 return new DynamicStruct(desc, ByteBuffer.allocate(desc.getSize())); 030 } 031 032 /** 033 * Constructs a new dynamic struct object with internal storage. The descriptor must be valid. The 034 * internal storage is allocated using ByteBuffer.allocateDirect(). 035 * 036 * @param desc struct descriptor 037 * @return dynamic struct object 038 * @throws IllegalStateException if struct descriptor is invalid 039 */ 040 public static DynamicStruct allocateDirect(StructDescriptor desc) { 041 return new DynamicStruct(desc, ByteBuffer.allocateDirect(desc.getSize())); 042 } 043 044 /** 045 * Constructs a new dynamic struct object. Note: the passed data buffer is not copied. 046 * Modifications to the passed buffer will be reflected in the struct and vice-versa. 047 * 048 * @param desc struct descriptor 049 * @param data byte buffer containing serialized data starting at current position 050 * @return dynamic struct object 051 */ 052 public static DynamicStruct wrap(StructDescriptor desc, ByteBuffer data) { 053 return new DynamicStruct(desc, data.slice()); 054 } 055 056 /** 057 * Gets the struct descriptor. 058 * 059 * @return struct descriptor 060 */ 061 public StructDescriptor getDescriptor() { 062 return m_desc; 063 } 064 065 /** 066 * Gets the serialized backing data buffer. 067 * 068 * @return data buffer 069 */ 070 public ByteBuffer getBuffer() { 071 return m_data.duplicate().position(0); 072 } 073 074 /** 075 * Overwrites the entire serialized struct by copying data from a byte array. 076 * 077 * @param data replacement data for the struct 078 * @throws BufferUnderflowException if data is smaller than the struct size 079 * @throws ReadOnlyBufferException if the underlying buffer is read-only 080 * @throws IllegalStateException if struct descriptor is invalid 081 */ 082 public void setData(byte[] data) { 083 if (data.length < m_desc.getSize()) { 084 throw new BufferUnderflowException(); 085 } 086 m_data.position(0).put(data); 087 } 088 089 /** 090 * Overwrites the entire serialized struct by copying data from a byte buffer. 091 * 092 * @param data replacement data for the struct; copy starts from current position 093 * @throws BufferUnderflowException if remaining data is smaller than the struct size 094 * @throws ReadOnlyBufferException if the underlying buffer is read-only 095 * @throws IllegalStateException if struct descriptor is invalid 096 */ 097 public void setData(ByteBuffer data) { 098 if (data.remaining() < m_desc.getSize()) { 099 throw new BufferUnderflowException(); 100 } 101 int oldLimit = data.limit(); 102 m_data.position(0).put(data.limit(m_desc.getSize())); 103 data.limit(oldLimit); 104 } 105 106 /** 107 * Gets a struct field descriptor by name. 108 * 109 * @param name field name 110 * @return field descriptor, or null if no field with that name exists 111 */ 112 public StructFieldDescriptor findField(String name) { 113 return m_desc.findFieldByName(name); 114 } 115 116 /** 117 * Gets the value of a boolean field. 118 * 119 * @param field field descriptor 120 * @param arrIndex array index (must be less than field array size) 121 * @return boolean field value 122 * @throws UnsupportedOperationException if field is not bool type 123 * @throws IllegalArgumentException if field is not a member of this struct 124 * @throws IllegalStateException if struct descriptor is invalid 125 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 126 */ 127 public boolean getBoolField(StructFieldDescriptor field, int arrIndex) { 128 if (field.getType() != StructFieldType.kBool) { 129 throw new UnsupportedOperationException("field is not bool type"); 130 } 131 return getFieldImpl(field, arrIndex) != 0; 132 } 133 134 /** 135 * Gets the value of a boolean field. 136 * 137 * @param field field descriptor 138 * @return boolean field value 139 * @throws UnsupportedOperationException if field is not bool type 140 * @throws IllegalArgumentException if field is not a member of this struct 141 * @throws IllegalStateException if struct descriptor is invalid 142 */ 143 public boolean getBoolField(StructFieldDescriptor field) { 144 return getBoolField(field, 0); 145 } 146 147 /** 148 * Sets the value of a boolean field. 149 * 150 * @param field field descriptor 151 * @param value boolean value 152 * @param arrIndex array index (must be less than field array size) 153 * @throws UnsupportedOperationException if field is not bool type 154 * @throws IllegalArgumentException if field is not a member of this struct 155 * @throws IllegalStateException if struct descriptor is invalid 156 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 157 * @throws ReadOnlyBufferException if the underlying buffer is read-only 158 */ 159 public void setBoolField(StructFieldDescriptor field, boolean value, int arrIndex) { 160 if (field.getType() != StructFieldType.kBool) { 161 throw new UnsupportedOperationException("field is not bool type"); 162 } 163 setFieldImpl(field, value ? 1 : 0, arrIndex); 164 } 165 166 /** 167 * Sets the value of a boolean field. 168 * 169 * @param field field descriptor 170 * @param value boolean value 171 * @throws UnsupportedOperationException if field is not bool type 172 * @throws IllegalArgumentException if field is not a member of this struct 173 * @throws IllegalStateException if struct descriptor is invalid 174 * @throws ReadOnlyBufferException if the underlying buffer is read-only 175 */ 176 public void setBoolField(StructFieldDescriptor field, boolean value) { 177 setBoolField(field, value, 0); 178 } 179 180 /** 181 * Gets the value of an integer field. 182 * 183 * @param field field descriptor 184 * @param arrIndex array index (must be less than field array size) 185 * @return integer field value 186 * @throws UnsupportedOperationException if field is not integer type 187 * @throws IllegalArgumentException if field is not a member of this struct 188 * @throws IllegalStateException if struct descriptor is invalid 189 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 190 */ 191 public long getIntField(StructFieldDescriptor field, int arrIndex) { 192 if (!field.isInt() && !field.isUint()) { 193 throw new UnsupportedOperationException("field is not integer type"); 194 } 195 return getFieldImpl(field, arrIndex); 196 } 197 198 /** 199 * Gets the value of an integer field. 200 * 201 * @param field field descriptor 202 * @return integer field value 203 * @throws UnsupportedOperationException if field is not integer type 204 * @throws IllegalArgumentException if field is not a member of this struct 205 * @throws IllegalStateException if struct descriptor is invalid 206 */ 207 public long getIntField(StructFieldDescriptor field) { 208 return getIntField(field, 0); 209 } 210 211 /** 212 * Sets the value of an integer field. 213 * 214 * @param field field descriptor 215 * @param value integer value 216 * @param arrIndex array index (must be less than field array size) 217 * @throws UnsupportedOperationException if field is not integer type 218 * @throws IllegalArgumentException if field is not a member of this struct 219 * @throws IllegalStateException if struct descriptor is invalid 220 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 221 * @throws ReadOnlyBufferException if the underlying buffer is read-only 222 */ 223 public void setIntField(StructFieldDescriptor field, long value, int arrIndex) { 224 if (!field.isInt() && !field.isUint()) { 225 throw new UnsupportedOperationException("field is not integer type"); 226 } 227 setFieldImpl(field, value, arrIndex); 228 } 229 230 /** 231 * Sets the value of an integer field. 232 * 233 * @param field field descriptor 234 * @param value integer value 235 * @throws UnsupportedOperationException if field is not integer type 236 * @throws IllegalArgumentException if field is not a member of this struct 237 * @throws IllegalStateException if struct descriptor is invalid 238 * @throws ReadOnlyBufferException if the underlying buffer is read-only 239 */ 240 public void setIntField(StructFieldDescriptor field, long value) { 241 setIntField(field, value, 0); 242 } 243 244 /** 245 * Gets the value of a float field. 246 * 247 * @param field field descriptor 248 * @param arrIndex array index (must be less than field array size) 249 * @return float field value 250 * @throws UnsupportedOperationException if field is not float type 251 * @throws IllegalArgumentException if field is not a member of this struct 252 * @throws IllegalStateException if struct descriptor is invalid 253 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 254 */ 255 public float getFloatField(StructFieldDescriptor field, int arrIndex) { 256 if (field.getType() != StructFieldType.kFloat) { 257 throw new UnsupportedOperationException("field is not float type"); 258 } 259 return Float.intBitsToFloat((int) getFieldImpl(field, arrIndex)); 260 } 261 262 /** 263 * Gets the value of a float field. 264 * 265 * @param field field descriptor 266 * @return float field value 267 * @throws UnsupportedOperationException if field is not float type 268 * @throws IllegalArgumentException if field is not a member of this struct 269 * @throws IllegalStateException if struct descriptor is invalid 270 */ 271 public float getFloatField(StructFieldDescriptor field) { 272 return getFloatField(field, 0); 273 } 274 275 /** 276 * Sets the value of a float field. 277 * 278 * @param field field descriptor 279 * @param value float value 280 * @param arrIndex array index (must be less than field array size) 281 * @throws UnsupportedOperationException if field is not float type 282 * @throws IllegalArgumentException if field is not a member of this struct 283 * @throws IllegalStateException if struct descriptor is invalid 284 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 285 * @throws ReadOnlyBufferException if the underlying buffer is read-only 286 */ 287 public void setFloatField(StructFieldDescriptor field, float value, int arrIndex) { 288 if (field.getType() != StructFieldType.kFloat) { 289 throw new UnsupportedOperationException("field is not float type"); 290 } 291 setFieldImpl(field, Float.floatToIntBits(value), arrIndex); 292 } 293 294 /** 295 * Sets the value of a float field. 296 * 297 * @param field field descriptor 298 * @param value float value 299 * @throws UnsupportedOperationException if field is not float type 300 * @throws IllegalArgumentException if field is not a member of this struct 301 * @throws IllegalStateException if struct descriptor is invalid 302 * @throws ReadOnlyBufferException if the underlying buffer is read-only 303 */ 304 public void setFloatField(StructFieldDescriptor field, float value) { 305 setFloatField(field, value, 0); 306 } 307 308 /** 309 * Gets the value of a double field. 310 * 311 * @param field field descriptor 312 * @param arrIndex array index (must be less than field array size) 313 * @return double field value 314 * @throws UnsupportedOperationException if field is not double type 315 * @throws IllegalArgumentException if field is not a member of this struct 316 * @throws IllegalStateException if struct descriptor is invalid 317 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 318 */ 319 public double getDoubleField(StructFieldDescriptor field, int arrIndex) { 320 if (field.getType() != StructFieldType.kDouble) { 321 throw new UnsupportedOperationException("field is not double type"); 322 } 323 return Double.longBitsToDouble(getFieldImpl(field, arrIndex)); 324 } 325 326 /** 327 * Gets the value of a double field. 328 * 329 * @param field field descriptor 330 * @return double field value 331 * @throws UnsupportedOperationException if field is not double type 332 * @throws IllegalArgumentException if field is not a member of this struct 333 * @throws IllegalStateException if struct descriptor is invalid 334 */ 335 public double getDoubleField(StructFieldDescriptor field) { 336 return getDoubleField(field, 0); 337 } 338 339 /** 340 * Sets the value of a double field. 341 * 342 * @param field field descriptor 343 * @param value double value 344 * @param arrIndex array index (must be less than field array size) 345 * @throws UnsupportedOperationException if field is not double type 346 * @throws IllegalArgumentException if field is not a member of this struct 347 * @throws IllegalStateException if struct descriptor is invalid 348 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 349 * @throws ReadOnlyBufferException if the underlying buffer is read-only 350 */ 351 public void setDoubleField(StructFieldDescriptor field, double value, int arrIndex) { 352 if (field.getType() != StructFieldType.kDouble) { 353 throw new UnsupportedOperationException("field is not double type"); 354 } 355 setFieldImpl(field, Double.doubleToLongBits(value), arrIndex); 356 } 357 358 /** 359 * Sets the value of a double field. 360 * 361 * @param field field descriptor 362 * @param value double value 363 * @throws UnsupportedOperationException if field is not double type 364 * @throws IllegalArgumentException if field is not a member of this struct 365 * @throws IllegalStateException if struct descriptor is invalid 366 * @throws ReadOnlyBufferException if the underlying buffer is read-only 367 */ 368 public void setDoubleField(StructFieldDescriptor field, double value) { 369 setDoubleField(field, value, 0); 370 } 371 372 /** 373 * Gets the value of a character or character array field. 374 * 375 * @param field field descriptor 376 * @return field value 377 * @throws UnsupportedOperationException if field is not char type 378 * @throws IllegalArgumentException if field is not a member of this struct 379 * @throws IllegalStateException if struct descriptor is invalid 380 */ 381 public String getStringField(StructFieldDescriptor field) { 382 if (field.getType() != StructFieldType.kChar) { 383 throw new UnsupportedOperationException("field is not char type"); 384 } 385 if (!field.getParent().equals(m_desc)) { 386 throw new IllegalArgumentException("field is not part of this struct"); 387 } 388 if (!m_desc.isValid()) { 389 throw new IllegalStateException("struct descriptor is not valid"); 390 } 391 byte[] bytes = new byte[field.m_arraySize]; 392 m_data.position(field.m_offset).get(bytes, 0, field.m_arraySize); 393 return new String(bytes, StandardCharsets.UTF_8); 394 } 395 396 /** 397 * Sets the value of a character or character array field. 398 * 399 * @param field field descriptor 400 * @param value field value 401 * @throws UnsupportedOperationException if field is not char type 402 * @throws IllegalArgumentException if field is not a member of this struct 403 * @throws IllegalStateException if struct descriptor is invalid 404 */ 405 public void setStringField(StructFieldDescriptor field, String value) { 406 if (field.getType() != StructFieldType.kChar) { 407 throw new UnsupportedOperationException("field is not char type"); 408 } 409 if (!field.getParent().equals(m_desc)) { 410 throw new IllegalArgumentException("field is not part of this struct"); 411 } 412 if (!m_desc.isValid()) { 413 throw new IllegalStateException("struct descriptor is not valid"); 414 } 415 ByteBuffer bb = StandardCharsets.UTF_8.encode(value); 416 int len = Math.min(bb.remaining(), field.m_arraySize); 417 m_data.position(field.m_offset).put(bb.limit(len)); 418 for (int i = len; i < field.m_arraySize; i++) { 419 m_data.put((byte) 0); 420 } 421 } 422 423 /** 424 * Gets the value of a struct field. 425 * 426 * @param field field descriptor 427 * @param arrIndex array index (must be less than field array size) 428 * @return field value 429 * @throws UnsupportedOperationException if field is not of struct type 430 * @throws IllegalArgumentException if field is not a member of this struct 431 * @throws IllegalStateException if struct descriptor is invalid 432 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 433 */ 434 public DynamicStruct getStructField(StructFieldDescriptor field, int arrIndex) { 435 if (field.getType() != StructFieldType.kStruct) { 436 throw new UnsupportedOperationException("field is not struct type"); 437 } 438 if (!field.getParent().equals(m_desc)) { 439 throw new IllegalArgumentException("field is not part of this struct"); 440 } 441 if (!m_desc.isValid()) { 442 throw new IllegalStateException("struct descriptor is not valid"); 443 } 444 if (arrIndex < 0 || arrIndex >= field.m_arraySize) { 445 throw new ArrayIndexOutOfBoundsException( 446 "arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")"); 447 } 448 StructDescriptor struct = field.getStruct(); 449 return wrap(struct, m_data.position(field.m_offset + arrIndex * struct.m_size)); 450 } 451 452 /** 453 * Gets the value of a struct field. 454 * 455 * @param field field descriptor 456 * @return field value 457 * @throws UnsupportedOperationException if field is not of struct type 458 * @throws IllegalArgumentException if field is not a member of this struct 459 * @throws IllegalStateException if struct descriptor is invalid 460 */ 461 public DynamicStruct getStructField(StructFieldDescriptor field) { 462 return getStructField(field, 0); 463 } 464 465 /** 466 * Sets the value of a struct field. 467 * 468 * @param field field descriptor 469 * @param value struct value 470 * @param arrIndex array index (must be less than field array size) 471 * @throws UnsupportedOperationException if field is not struct type 472 * @throws IllegalArgumentException if field is not a member of this struct 473 * @throws IllegalStateException if struct descriptor is invalid 474 * @throws ArrayIndexOutOfBoundsException if array index is out of bounds 475 * @throws ReadOnlyBufferException if the underlying buffer is read-only 476 */ 477 public void setStructField(StructFieldDescriptor field, DynamicStruct value, int arrIndex) { 478 if (field.getType() != StructFieldType.kStruct) { 479 throw new UnsupportedOperationException("field is not struct type"); 480 } 481 if (!field.getParent().equals(m_desc)) { 482 throw new IllegalArgumentException("field is not part of this struct"); 483 } 484 if (!m_desc.isValid()) { 485 throw new IllegalStateException("struct descriptor is not valid"); 486 } 487 StructDescriptor struct = field.getStruct(); 488 if (!value.getDescriptor().equals(struct)) { 489 throw new IllegalArgumentException("value's struct type does not match field struct type"); 490 } 491 if (!value.getDescriptor().isValid()) { 492 throw new IllegalStateException("value's struct descriptor is not valid"); 493 } 494 if (arrIndex < 0 || arrIndex >= field.m_arraySize) { 495 throw new ArrayIndexOutOfBoundsException( 496 "arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")"); 497 } 498 m_data 499 .position(field.m_offset + arrIndex * struct.m_size) 500 .put(value.m_data.position(0).limit(value.getDescriptor().getSize())); 501 } 502 503 /** 504 * Sets the value of a struct field. 505 * 506 * @param field field descriptor 507 * @param value struct value 508 * @throws UnsupportedOperationException if field is not struct type 509 * @throws IllegalArgumentException if field is not a member of this struct 510 * @throws IllegalStateException if struct descriptor is invalid 511 * @throws ReadOnlyBufferException if the underlying buffer is read-only 512 */ 513 public void setStructField(StructFieldDescriptor field, DynamicStruct value) { 514 setStructField(field, value, 0); 515 } 516 517 private long getFieldImpl(StructFieldDescriptor field, int arrIndex) { 518 if (!field.getParent().equals(m_desc)) { 519 throw new IllegalArgumentException("field is not part of this struct"); 520 } 521 if (!m_desc.isValid()) { 522 throw new IllegalStateException("struct descriptor is not valid"); 523 } 524 if (arrIndex < 0 || arrIndex >= field.m_arraySize) { 525 throw new ArrayIndexOutOfBoundsException( 526 "arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")"); 527 } 528 529 long val; 530 switch (field.m_size) { 531 case 1: 532 val = m_data.get(field.m_offset + arrIndex); 533 break; 534 case 2: 535 val = m_data.getShort(field.m_offset + arrIndex * 2); 536 break; 537 case 4: 538 val = m_data.getInt(field.m_offset + arrIndex * 4); 539 break; 540 case 8: 541 val = m_data.getLong(field.m_offset + arrIndex * 8); 542 break; 543 default: 544 throw new IllegalStateException("invalid field size"); 545 } 546 547 if (field.isUint() || field.getType() == StructFieldType.kBool) { 548 // for unsigned fields, we can simply logical shift and mask 549 return (val >>> field.m_bitShift) & field.getBitMask(); 550 } else { 551 // to get sign extension, shift so the sign bit within the bitfield goes to the long's sign 552 // bit (also clearing all higher bits), then shift back down (also clearing all lower bits); 553 // since upper and lower bits are cleared with the shifts, the bitmask is unnecessary 554 return (val << (64 - field.m_bitShift - field.getBitWidth())) >> (64 - field.getBitWidth()); 555 } 556 } 557 558 private void setFieldImpl(StructFieldDescriptor field, long value, int arrIndex) { 559 if (!field.getParent().equals(m_desc)) { 560 throw new IllegalArgumentException("field is not part of this struct"); 561 } 562 if (!m_desc.isValid()) { 563 throw new IllegalStateException("struct descriptor is not valid"); 564 } 565 if (arrIndex < 0 || arrIndex >= field.m_arraySize) { 566 throw new ArrayIndexOutOfBoundsException( 567 "arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")"); 568 } 569 570 // common case is no bit shift and no masking 571 if (!field.isBitField()) { 572 switch (field.m_size) { 573 case 1: 574 m_data.put(field.m_offset + arrIndex, (byte) value); 575 break; 576 case 2: 577 m_data.putShort(field.m_offset + arrIndex * 2, (short) value); 578 break; 579 case 4: 580 m_data.putInt(field.m_offset + arrIndex * 4, (int) value); 581 break; 582 case 8: 583 m_data.putLong(field.m_offset + arrIndex * 8, value); 584 break; 585 default: 586 throw new IllegalStateException("invalid field size"); 587 } 588 return; 589 } 590 591 // handle bit shifting and masking into current value 592 switch (field.m_size) { 593 case 1: 594 { 595 byte val = m_data.get(field.m_offset + arrIndex); 596 val &= (byte) ~(field.getBitMask() << field.m_bitShift); 597 val |= (byte) ((value & field.getBitMask()) << field.m_bitShift); 598 m_data.put(field.m_offset + arrIndex, val); 599 break; 600 } 601 case 2: 602 { 603 short val = m_data.getShort(field.m_offset + arrIndex * 2); 604 val &= (short) ~(field.getBitMask() << field.m_bitShift); 605 val |= (short) ((value & field.getBitMask()) << field.m_bitShift); 606 m_data.putShort(field.m_offset + arrIndex * 2, val); 607 break; 608 } 609 case 4: 610 { 611 int val = m_data.getInt(field.m_offset + arrIndex * 4); 612 val &= (int) ~(field.getBitMask() << field.m_bitShift); 613 val |= (int) ((value & field.getBitMask()) << field.m_bitShift); 614 m_data.putInt(field.m_offset + arrIndex * 4, val); 615 break; 616 } 617 case 8: 618 { 619 long val = m_data.getLong(field.m_offset + arrIndex * 8); 620 val &= ~(field.getBitMask() << field.m_bitShift); 621 val |= (value & field.getBitMask()) << field.m_bitShift; 622 m_data.putLong(field.m_offset + arrIndex * 8, val); 623 break; 624 } 625 default: 626 throw new IllegalStateException("invalid field size"); 627 } 628 } 629 630 private final StructDescriptor m_desc; 631 private final ByteBuffer m_data; 632}