SCRIPT Move java files

This commit is contained in:
PJ Reiniger
2025-11-07 19:55:40 -05:00
committed by Peter Johnson
parent 7ca1be9bae
commit c350c5f112
1486 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
/** Exception thrown when encountering a bad schema. */
public class BadSchemaException extends Exception {
/** The bad schema field. */
private final String m_field;
/**
* Constructs a BadSchemaException.
*
* @param message the detail message.
*/
public BadSchemaException(String message) {
super(message);
m_field = "";
}
/**
* Constructs a BadSchemaException.
*
* @param message the detail message.
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
*/
public BadSchemaException(String message, Throwable cause) {
super(message, cause);
m_field = "";
}
/**
* Constructs a BadSchemaException.
*
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
*/
public BadSchemaException(Throwable cause) {
super(cause);
m_field = "";
}
/**
* Constructs a BadSchemaException.
*
* @param field The bad schema field.
* @param message the detail message.
*/
public BadSchemaException(String field, String message) {
super(message);
m_field = field;
}
/**
* Constructs a BadSchemaException.
*
* @param field The bad schema field.
* @param message the detail message.
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
*/
public BadSchemaException(String field, String message, Throwable cause) {
super(message, cause);
m_field = field;
}
/**
* Gets the name of the bad schema field.
*
* @return The name of the bad schema field, or an empty string if not applicable.
*/
public String getField() {
return m_field;
}
@Override
public String getMessage() {
return m_field.isEmpty() ? super.getMessage() : "field " + m_field + ": " + super.getMessage();
}
}

View File

@@ -0,0 +1,658 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ReadOnlyBufferException;
import java.nio.charset.StandardCharsets;
/** Dynamic (run-time) access to a serialized raw struct. */
public final class DynamicStruct {
private DynamicStruct(StructDescriptor desc, ByteBuffer data) {
m_desc = desc;
m_data = data.order(ByteOrder.LITTLE_ENDIAN);
}
/**
* Constructs a new dynamic struct object with internal storage. The descriptor must be valid. The
* internal storage is allocated using ByteBuffer.allocate().
*
* @param desc struct descriptor
* @return dynamic struct object
* @throws IllegalStateException if struct descriptor is invalid
*/
public static DynamicStruct allocate(StructDescriptor desc) {
return new DynamicStruct(desc, ByteBuffer.allocate(desc.getSize()));
}
/**
* Constructs a new dynamic struct object with internal storage. The descriptor must be valid. The
* internal storage is allocated using ByteBuffer.allocateDirect().
*
* @param desc struct descriptor
* @return dynamic struct object
* @throws IllegalStateException if struct descriptor is invalid
*/
public static DynamicStruct allocateDirect(StructDescriptor desc) {
return new DynamicStruct(desc, ByteBuffer.allocateDirect(desc.getSize()));
}
/**
* Constructs a new dynamic struct object. Note: the passed data buffer is not copied.
* Modifications to the passed buffer will be reflected in the struct and vice-versa.
*
* @param desc struct descriptor
* @param data byte buffer containing serialized data starting at current position
* @return dynamic struct object
*/
public static DynamicStruct wrap(StructDescriptor desc, ByteBuffer data) {
return new DynamicStruct(desc, data.slice());
}
/**
* Gets the struct descriptor.
*
* @return struct descriptor
*/
public StructDescriptor getDescriptor() {
return m_desc;
}
/**
* Gets the serialized backing data buffer.
*
* @return data buffer
*/
public ByteBuffer getBuffer() {
return m_data.duplicate().position(0);
}
/**
* Overwrites the entire serialized struct by copying data from a byte array.
*
* @param data replacement data for the struct
* @throws BufferUnderflowException if data is smaller than the struct size
* @throws ReadOnlyBufferException if the underlying buffer is read-only
* @throws IllegalStateException if struct descriptor is invalid
*/
public void setData(byte[] data) {
if (data.length < m_desc.getSize()) {
throw new BufferUnderflowException();
}
m_data.position(0).put(data);
}
/**
* Overwrites the entire serialized struct by copying data from a byte buffer.
*
* @param data replacement data for the struct; copy starts from current position
* @throws BufferUnderflowException if remaining data is smaller than the struct size
* @throws ReadOnlyBufferException if the underlying buffer is read-only
* @throws IllegalStateException if struct descriptor is invalid
*/
public void setData(ByteBuffer data) {
if (data.remaining() < m_desc.getSize()) {
throw new BufferUnderflowException();
}
int oldLimit = data.limit();
m_data.position(0).put(data.limit(m_desc.getSize()));
data.limit(oldLimit);
}
/**
* Gets a struct field descriptor by name.
*
* @param name field name
* @return field descriptor, or null if no field with that name exists
*/
public StructFieldDescriptor findField(String name) {
return m_desc.findFieldByName(name);
}
/**
* Gets the value of a boolean field.
*
* @param field field descriptor
* @param arrIndex array index (must be less than field array size)
* @return boolean field value
* @throws UnsupportedOperationException if field is not bool type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
*/
public boolean getBoolField(StructFieldDescriptor field, int arrIndex) {
if (field.getType() != StructFieldType.kBool) {
throw new UnsupportedOperationException("field is not bool type");
}
return getFieldImpl(field, arrIndex) != 0;
}
/**
* Gets the value of a boolean field.
*
* @param field field descriptor
* @return boolean field value
* @throws UnsupportedOperationException if field is not bool type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
*/
public boolean getBoolField(StructFieldDescriptor field) {
return getBoolField(field, 0);
}
/**
* Sets the value of a boolean field.
*
* @param field field descriptor
* @param value boolean value
* @param arrIndex array index (must be less than field array size)
* @throws UnsupportedOperationException if field is not bool type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setBoolField(StructFieldDescriptor field, boolean value, int arrIndex) {
if (field.getType() != StructFieldType.kBool) {
throw new UnsupportedOperationException("field is not bool type");
}
setFieldImpl(field, value ? 1 : 0, arrIndex);
}
/**
* Sets the value of a boolean field.
*
* @param field field descriptor
* @param value boolean value
* @throws UnsupportedOperationException if field is not bool type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setBoolField(StructFieldDescriptor field, boolean value) {
setBoolField(field, value, 0);
}
/**
* Gets the value of an integer field.
*
* @param field field descriptor
* @param arrIndex array index (must be less than field array size)
* @return integer field value
* @throws UnsupportedOperationException if field is not integer type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
*/
public long getIntField(StructFieldDescriptor field, int arrIndex) {
if (!field.isInt() && !field.isUint()) {
throw new UnsupportedOperationException("field is not integer type");
}
return getFieldImpl(field, arrIndex);
}
/**
* Gets the value of an integer field.
*
* @param field field descriptor
* @return integer field value
* @throws UnsupportedOperationException if field is not integer type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
*/
public long getIntField(StructFieldDescriptor field) {
return getIntField(field, 0);
}
/**
* Sets the value of an integer field.
*
* @param field field descriptor
* @param value integer value
* @param arrIndex array index (must be less than field array size)
* @throws UnsupportedOperationException if field is not integer type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setIntField(StructFieldDescriptor field, long value, int arrIndex) {
if (!field.isInt() && !field.isUint()) {
throw new UnsupportedOperationException("field is not integer type");
}
setFieldImpl(field, value, arrIndex);
}
/**
* Sets the value of an integer field.
*
* @param field field descriptor
* @param value integer value
* @throws UnsupportedOperationException if field is not integer type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setIntField(StructFieldDescriptor field, long value) {
setIntField(field, value, 0);
}
/**
* Gets the value of a float field.
*
* @param field field descriptor
* @param arrIndex array index (must be less than field array size)
* @return float field value
* @throws UnsupportedOperationException if field is not float type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
*/
public float getFloatField(StructFieldDescriptor field, int arrIndex) {
if (field.getType() != StructFieldType.kFloat) {
throw new UnsupportedOperationException("field is not float type");
}
return Float.intBitsToFloat((int) getFieldImpl(field, arrIndex));
}
/**
* Gets the value of a float field.
*
* @param field field descriptor
* @return float field value
* @throws UnsupportedOperationException if field is not float type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
*/
public float getFloatField(StructFieldDescriptor field) {
return getFloatField(field, 0);
}
/**
* Sets the value of a float field.
*
* @param field field descriptor
* @param value float value
* @param arrIndex array index (must be less than field array size)
* @throws UnsupportedOperationException if field is not float type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setFloatField(StructFieldDescriptor field, float value, int arrIndex) {
if (field.getType() != StructFieldType.kFloat) {
throw new UnsupportedOperationException("field is not float type");
}
setFieldImpl(field, Float.floatToIntBits(value), arrIndex);
}
/**
* Sets the value of a float field.
*
* @param field field descriptor
* @param value float value
* @throws UnsupportedOperationException if field is not float type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setFloatField(StructFieldDescriptor field, float value) {
setFloatField(field, value, 0);
}
/**
* Gets the value of a double field.
*
* @param field field descriptor
* @param arrIndex array index (must be less than field array size)
* @return double field value
* @throws UnsupportedOperationException if field is not double type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
*/
public double getDoubleField(StructFieldDescriptor field, int arrIndex) {
if (field.getType() != StructFieldType.kDouble) {
throw new UnsupportedOperationException("field is not double type");
}
return Double.longBitsToDouble(getFieldImpl(field, arrIndex));
}
/**
* Gets the value of a double field.
*
* @param field field descriptor
* @return double field value
* @throws UnsupportedOperationException if field is not double type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
*/
public double getDoubleField(StructFieldDescriptor field) {
return getDoubleField(field, 0);
}
/**
* Sets the value of a double field.
*
* @param field field descriptor
* @param value double value
* @param arrIndex array index (must be less than field array size)
* @throws UnsupportedOperationException if field is not double type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setDoubleField(StructFieldDescriptor field, double value, int arrIndex) {
if (field.getType() != StructFieldType.kDouble) {
throw new UnsupportedOperationException("field is not double type");
}
setFieldImpl(field, Double.doubleToLongBits(value), arrIndex);
}
/**
* Sets the value of a double field.
*
* @param field field descriptor
* @param value double value
* @throws UnsupportedOperationException if field is not double type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setDoubleField(StructFieldDescriptor field, double value) {
setDoubleField(field, value, 0);
}
/**
* Gets the value of a character or character array field.
*
* @param field field descriptor
* @return field value
* @throws UnsupportedOperationException if field is not char type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
*/
@SuppressWarnings({"PMD.CollapsibleIfStatements", "PMD.AvoidDeeplyNestedIfStmts"})
public String getStringField(StructFieldDescriptor field) {
if (field.getType() != StructFieldType.kChar) {
throw new UnsupportedOperationException("field is not char type");
}
if (!field.getParent().equals(m_desc)) {
throw new IllegalArgumentException("field is not part of this struct");
}
if (!m_desc.isValid()) {
throw new IllegalStateException("struct descriptor is not valid");
}
byte[] bytes = new byte[field.m_arraySize];
m_data.position(field.m_offset).get(bytes, 0, field.m_arraySize);
// Find last non zero character
int stringLength = bytes.length;
for (; stringLength > 0; stringLength--) {
if (bytes[stringLength - 1] != 0) {
break;
}
}
// If string is all zeroes, its empty and return an empty string.
if (stringLength == 0) {
return "";
}
// Check if the end of the string is in the middle of a continuation byte or
// not.
if ((bytes[stringLength - 1] & 0x80) != 0) {
// This is a UTF8 continuation byte. Make sure its valid.
// Walk back until initial byte is found
int utf8StartByte = stringLength;
for (; utf8StartByte > 0; utf8StartByte--) {
if ((bytes[utf8StartByte - 1] & 0x40) != 0) {
// Having 2nd bit set means start byte
break;
}
}
if (utf8StartByte == 0) {
// This case means string only contains continuation bytes
return "";
}
utf8StartByte--;
// Check if its a 2, 3, or 4 byte
byte checkByte = bytes[utf8StartByte];
if ((checkByte & 0xE0) == 0xC0) {
// 2 byte, need 1 more byte
if (utf8StartByte != stringLength - 2) {
stringLength = utf8StartByte;
}
} else if ((checkByte & 0xF0) == 0xE0) {
// 3 byte, need 2 more bytes
if (utf8StartByte != stringLength - 3) {
stringLength = utf8StartByte;
}
} else if ((checkByte & 0xF8) == 0xF0) {
// 4 byte, need 3 more bytes
if (utf8StartByte != stringLength - 4) {
stringLength = utf8StartByte;
}
}
// If we get here, the string is either completely garbage or fine.
}
return new String(bytes, 0, stringLength, StandardCharsets.UTF_8);
}
/**
* Sets the value of a character or character array field.
*
* @param field field descriptor
* @param value field value
* @return true if the full value fit in the struct, false if truncated
* @throws UnsupportedOperationException if field is not char type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
*/
public boolean setStringField(StructFieldDescriptor field, String value) {
if (field.getType() != StructFieldType.kChar) {
throw new UnsupportedOperationException("field is not char type");
}
if (!field.getParent().equals(m_desc)) {
throw new IllegalArgumentException("field is not part of this struct");
}
if (!m_desc.isValid()) {
throw new IllegalStateException("struct descriptor is not valid");
}
ByteBuffer bb = StandardCharsets.UTF_8.encode(value);
int len = Math.min(bb.remaining(), field.m_arraySize);
boolean copiedFull = len == bb.remaining();
m_data.position(field.m_offset).put(bb.limit(len));
for (int i = len; i < field.m_arraySize; i++) {
m_data.put((byte) 0);
}
return copiedFull;
}
/**
* Gets the value of a struct field.
*
* @param field field descriptor
* @param arrIndex array index (must be less than field array size)
* @return field value
* @throws UnsupportedOperationException if field is not of struct type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
*/
public DynamicStruct getStructField(StructFieldDescriptor field, int arrIndex) {
if (field.getType() != StructFieldType.kStruct) {
throw new UnsupportedOperationException("field is not struct type");
}
if (!field.getParent().equals(m_desc)) {
throw new IllegalArgumentException("field is not part of this struct");
}
if (!m_desc.isValid()) {
throw new IllegalStateException("struct descriptor is not valid");
}
if (arrIndex < 0 || arrIndex >= field.m_arraySize) {
throw new ArrayIndexOutOfBoundsException(
"arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")");
}
StructDescriptor struct = field.getStruct();
return wrap(struct, m_data.position(field.m_offset + arrIndex * struct.m_size));
}
/**
* Gets the value of a struct field.
*
* @param field field descriptor
* @return field value
* @throws UnsupportedOperationException if field is not of struct type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
*/
public DynamicStruct getStructField(StructFieldDescriptor field) {
return getStructField(field, 0);
}
/**
* Sets the value of a struct field.
*
* @param field field descriptor
* @param value struct value
* @param arrIndex array index (must be less than field array size)
* @throws UnsupportedOperationException if field is not struct type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ArrayIndexOutOfBoundsException if array index is out of bounds
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setStructField(StructFieldDescriptor field, DynamicStruct value, int arrIndex) {
if (field.getType() != StructFieldType.kStruct) {
throw new UnsupportedOperationException("field is not struct type");
}
if (!field.getParent().equals(m_desc)) {
throw new IllegalArgumentException("field is not part of this struct");
}
if (!m_desc.isValid()) {
throw new IllegalStateException("struct descriptor is not valid");
}
StructDescriptor struct = field.getStruct();
if (!value.getDescriptor().equals(struct)) {
throw new IllegalArgumentException("value's struct type does not match field struct type");
}
if (!value.getDescriptor().isValid()) {
throw new IllegalStateException("value's struct descriptor is not valid");
}
if (arrIndex < 0 || arrIndex >= field.m_arraySize) {
throw new ArrayIndexOutOfBoundsException(
"arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")");
}
m_data
.position(field.m_offset + arrIndex * struct.m_size)
.put(value.m_data.position(0).limit(value.getDescriptor().getSize()));
}
/**
* Sets the value of a struct field.
*
* @param field field descriptor
* @param value struct value
* @throws UnsupportedOperationException if field is not struct type
* @throws IllegalArgumentException if field is not a member of this struct
* @throws IllegalStateException if struct descriptor is invalid
* @throws ReadOnlyBufferException if the underlying buffer is read-only
*/
public void setStructField(StructFieldDescriptor field, DynamicStruct value) {
setStructField(field, value, 0);
}
private long getFieldImpl(StructFieldDescriptor field, int arrIndex) {
if (!field.getParent().equals(m_desc)) {
throw new IllegalArgumentException("field is not part of this struct");
}
if (!m_desc.isValid()) {
throw new IllegalStateException("struct descriptor is not valid");
}
if (arrIndex < 0 || arrIndex >= field.m_arraySize) {
throw new ArrayIndexOutOfBoundsException(
"arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")");
}
long val =
switch (field.m_size) {
case 1 -> m_data.get(field.m_offset + arrIndex);
case 2 -> m_data.getShort(field.m_offset + arrIndex * 2);
case 4 -> m_data.getInt(field.m_offset + arrIndex * 4);
case 8 -> m_data.getLong(field.m_offset + arrIndex * 8);
default -> throw new IllegalStateException("invalid field size");
};
if (field.isUint() || field.getType() == StructFieldType.kBool) {
// for unsigned fields, we can simply logical shift and mask
return (val >>> field.m_bitShift) & field.getBitMask();
} else {
// to get sign extension, shift so the sign bit within the bitfield goes to the long's sign
// bit (also clearing all higher bits), then shift back down (also clearing all lower bits);
// since upper and lower bits are cleared with the shifts, the bitmask is unnecessary
return (val << (64 - field.m_bitShift - field.getBitWidth())) >> (64 - field.getBitWidth());
}
}
private void setFieldImpl(StructFieldDescriptor field, long value, int arrIndex) {
if (!field.getParent().equals(m_desc)) {
throw new IllegalArgumentException("field is not part of this struct");
}
if (!m_desc.isValid()) {
throw new IllegalStateException("struct descriptor is not valid");
}
if (arrIndex < 0 || arrIndex >= field.m_arraySize) {
throw new ArrayIndexOutOfBoundsException(
"arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")");
}
// common case is no bit shift and no masking
if (!field.isBitField()) {
switch (field.m_size) {
case 1 -> m_data.put(field.m_offset + arrIndex, (byte) value);
case 2 -> m_data.putShort(field.m_offset + arrIndex * 2, (short) value);
case 4 -> m_data.putInt(field.m_offset + arrIndex * 4, (int) value);
case 8 -> m_data.putLong(field.m_offset + arrIndex * 8, value);
default -> throw new IllegalStateException("invalid field size");
}
return;
}
// handle bit shifting and masking into current value
switch (field.m_size) {
case 1 -> {
byte val = m_data.get(field.m_offset + arrIndex);
val &= (byte) ~(field.getBitMask() << field.m_bitShift);
val |= (byte) ((value & field.getBitMask()) << field.m_bitShift);
m_data.put(field.m_offset + arrIndex, val);
}
case 2 -> {
short val = m_data.getShort(field.m_offset + arrIndex * 2);
val &= (short) ~(field.getBitMask() << field.m_bitShift);
val |= (short) ((value & field.getBitMask()) << field.m_bitShift);
m_data.putShort(field.m_offset + arrIndex * 2, val);
}
case 4 -> {
int val = m_data.getInt(field.m_offset + arrIndex * 4);
val &= (int) ~(field.getBitMask() << field.m_bitShift);
val |= (int) ((value & field.getBitMask()) << field.m_bitShift);
m_data.putInt(field.m_offset + arrIndex * 4, val);
}
case 8 -> {
long val = m_data.getLong(field.m_offset + arrIndex * 8);
val &= ~(field.getBitMask() << field.m_bitShift);
val |= (value & field.getBitMask()) << field.m_bitShift;
m_data.putLong(field.m_offset + arrIndex * 8, val);
}
default -> throw new IllegalStateException("invalid field size");
}
}
private final StructDescriptor m_desc;
private final ByteBuffer m_data;
}

View File

@@ -0,0 +1,227 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
/**
* Interface for raw struct serialization.
*
* <p>This is designed for serializing small fixed-size data structures in the fastest and most
* compact means possible. Serialization consists of making relative put() calls to a ByteBuffer and
* deserialization consists of making relative get() calls from a ByteBuffer.
*
* <p>Idiomatically, classes that support raw struct serialization should provide a static final
* member named "struct" that provides an instance of an implementation of this interface.
*
* @param <T> object type
*/
public interface Struct<T> {
/** Serialized size of a "bool" value. */
int kSizeBool = 1;
/** Serialized size of an "int8" or "uint8" value. */
int kSizeInt8 = 1;
/** Serialized size of an "int16" or "uint16" value. */
int kSizeInt16 = 2;
/** Serialized size of an "int32" or "uint32" value. */
int kSizeInt32 = 4;
/** Serialized size of an "int64" or "uint64" value. */
int kSizeInt64 = 8;
/** Serialized size of an "float" or "float32" value. */
int kSizeFloat = 4;
/** Serialized size of an "double" or "float64" value. */
int kSizeDouble = 8;
/**
* Gets the Class object for the stored value.
*
* @return Class
*/
Class<T> getTypeClass();
/**
* Gets the type name (e.g. for schemas of other structs). This should be globally unique among
* structs.
*
* @return type name
*/
String getTypeName();
/**
* Gets the type string (e.g. for NetworkTables). This should be globally unique and start with
* "struct:".
*
* @return type string
*/
default String getTypeString() {
return "struct:" + getTypeName();
}
/**
* Gets the serialized size (in bytes). This should always be a constant.
*
* @return serialized size
*/
int getSize();
/**
* Gets the schema.
*
* @return schema
*/
String getSchema();
/**
* Gets the list of struct types referenced by this struct.
*
* @return list of struct types
*/
default Struct<?>[] getNested() {
return new Struct<?>[] {};
}
/**
* Deserializes an object from a raw struct serialized ByteBuffer starting at the current
* position. Will increment the ByteBuffer position by getStructSize() bytes. Will not otherwise
* modify the ByteBuffer (e.g. byte order will not be changed).
*
* @param bb ByteBuffer
* @return New object
*/
T unpack(ByteBuffer bb);
/**
* Puts object contents to a ByteBuffer starting at the current position. Will increment the
* ByteBuffer position by getStructSize() bytes. Will not otherwise modify the ByteBuffer (e.g.
* byte order will not be changed).
*
* @param bb ByteBuffer
* @param value object to serialize
*/
void pack(ByteBuffer bb, T value);
/**
* Updates object contents from a raw struct serialized ByteBuffer starting at the current
* position. Will increment the ByteBuffer position by getStructSize() bytes. Will not otherwise
* modify the ByteBuffer (e.g. byte order will not be changed).
*
* <p>Immutable classes cannot and should not implement this function. The default implementation
* throws UnsupportedOperationException.
*
* @param out object to update
* @param bb ByteBuffer
* @throws UnsupportedOperationException if the object is immutable
*/
default void unpackInto(T out, ByteBuffer bb) {
throw new UnsupportedOperationException("object does not support unpackInto");
}
/**
* Deserializes an array from a raw struct serialized ByteBuffer starting at the current position.
* Will increment the ByteBuffer position by size * struct.size() bytes. Will not otherwise modify
* the ByteBuffer (e.g. byte order will not be changed).
*
* @param <T> Object type
* @param bb ByteBuffer
* @param size Size of the array
* @param struct Struct implementation
* @return Deserialized array
*/
static <T> T[] unpackArray(ByteBuffer bb, int size, Struct<T> struct) {
@SuppressWarnings("unchecked")
T[] arr = (T[]) Array.newInstance(struct.getTypeClass(), size);
for (int i = 0; i < arr.length; i++) {
arr[i] = struct.unpack(bb);
}
return arr;
}
/**
* Deserializes a double array from a raw struct serialized ByteBuffer starting at the current
* position. Will increment the ByteBuffer position by size * kSizeDouble bytes. Will not
* otherwise modify the ByteBuffer (e.g. byte order will not be changed).
*
* @param bb ByteBuffer
* @param size Size of the array
* @return Double array
*/
static double[] unpackDoubleArray(ByteBuffer bb, int size) {
double[] arr = new double[size];
for (int i = 0; i < size; i++) {
arr[i] = bb.getDouble();
}
return arr;
}
/**
* Puts array contents to a ByteBuffer starting at the current position. Will increment the
* ByteBuffer position by size * struct.size() bytes. Will not otherwise modify the ByteBuffer
* (e.g. byte order will not be changed).
*
* @param <T> Object type
* @param bb ByteBuffer
* @param arr Array to serialize
* @param struct Struct implementation
*/
static <T> void packArray(ByteBuffer bb, T[] arr, Struct<T> struct) {
for (T obj : arr) {
struct.pack(bb, obj);
}
}
/**
* Puts array contents to a ByteBuffer starting at the current position. Will increment the
* ByteBuffer position by size * kSizeDouble bytes. Will not otherwise modify the ByteBuffer (e.g.
* byte order will not be changed).
*
* @param bb ByteBuffer
* @param arr Array to serialize
*/
static void packArray(ByteBuffer bb, double[] arr) {
for (double obj : arr) {
bb.putDouble(obj);
}
}
/**
* Returns whether or not objects are immutable. Immutable objects must also be comparable using
* the equals() method. Default implementation returns false.
*
* @return True if object is immutable
*/
default boolean isImmutable() {
return false;
}
/**
* Returns whether or not objects are cloneable using the clone() method. Cloneable objects must
* also be comparable using the equals() method. Default implementation returns false.
*
* @return True if object is cloneable
*/
default boolean isCloneable() {
return false;
}
/**
* Creates a (deep) clone of the object. May also return the object directly if the object is
* immutable. Default implementation throws CloneNotSupportedException. Typically this should be
* implemented by implementing clone() on the object itself, and calling it from here.
*
* @param obj object to clone
* @return Clone of object (if immutable, may be same object)
* @throws CloneNotSupportedException if clone not supported
*/
default T clone(T obj) throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}

View File

@@ -0,0 +1,231 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collection;
/**
* Reusable buffer for serialization/deserialization to/from a raw struct.
*
* @param <T> Object type.
*/
public final class StructBuffer<T> {
private StructBuffer(Struct<T> struct) {
m_structSize = struct.getSize();
m_buf = ByteBuffer.allocateDirect(m_structSize).order(ByteOrder.LITTLE_ENDIAN);
m_struct = struct;
}
/**
* Returns a StructBuffer for the given struct.
*
* @param struct A struct.
* @param <T> Object type.
* @return A StructBuffer for the given struct.
*/
public static <T> StructBuffer<T> create(Struct<T> struct) {
return new StructBuffer<>(struct);
}
/**
* Gets the struct object of the stored type.
*
* @return struct object
*/
public Struct<T> getStruct() {
return m_struct;
}
/**
* Gets the type string.
*
* @return type string
*/
public String getTypeString() {
return m_struct.getTypeString();
}
/**
* Ensures sufficient buffer space is available for the given number of elements.
*
* @param nelem number of elements
*/
public void reserve(int nelem) {
if ((nelem * m_structSize) > m_buf.capacity()) {
m_buf = ByteBuffer.allocateDirect(nelem * m_structSize).order(ByteOrder.LITTLE_ENDIAN);
}
}
/**
* Serializes a value to a ByteBuffer. The returned ByteBuffer is a direct byte buffer with the
* position set to the end of the serialized data.
*
* @param value value
* @return byte buffer
*/
public ByteBuffer write(T value) {
m_buf.position(0);
m_struct.pack(m_buf, value);
return m_buf;
}
/**
* Deserializes a value from a byte array, creating a new object.
*
* @param buf byte array
* @param start starting location within byte array
* @param len length of serialized data
* @return new object
*/
public T read(byte[] buf, int start, int len) {
return read(ByteBuffer.wrap(buf, start, len));
}
/**
* Deserializes a value from a byte array, creating a new object.
*
* @param buf byte array
* @return new object
*/
public T read(byte[] buf) {
return read(buf, 0, buf.length);
}
/**
* Deserializes a value from a ByteBuffer, creating a new object.
*
* @param buf byte buffer
* @return new object
*/
public T read(ByteBuffer buf) {
buf.order(ByteOrder.LITTLE_ENDIAN);
return m_struct.unpack(buf);
}
/**
* Deserializes a value from a byte array into a mutable object.
*
* @param out object (will be updated with deserialized contents)
* @param buf byte array
* @param start starting location within byte array
* @param len length of serialized data
* @throws UnsupportedOperationException if T is immutable
*/
public void readInto(T out, byte[] buf, int start, int len) {
readInto(out, ByteBuffer.wrap(buf, start, len));
}
/**
* Deserializes a value from a byte array into a mutable object.
*
* @param out object (will be updated with deserialized contents)
* @param buf byte array
* @throws UnsupportedOperationException if T is immutable
*/
public void readInto(T out, byte[] buf) {
readInto(out, buf, 0, buf.length);
}
/**
* Deserializes a value from a ByteBuffer into a mutable object.
*
* @param out object (will be updated with deserialized contents)
* @param buf byte buffer
* @throws UnsupportedOperationException if T is immutable
*/
public void readInto(T out, ByteBuffer buf) {
m_struct.unpackInto(out, buf);
}
/**
* Serializes a collection of values to a ByteBuffer. The returned ByteBuffer is a direct byte
* buffer with the position set to the end of the serialized data.
*
* @param values values
* @return byte buffer
*/
public ByteBuffer writeArray(Collection<T> values) {
m_buf.position(0);
if ((values.size() * m_structSize) > m_buf.capacity()) {
m_buf =
ByteBuffer.allocateDirect(values.size() * m_structSize * 2)
.order(ByteOrder.LITTLE_ENDIAN);
}
for (T v : values) {
m_struct.pack(m_buf, v);
}
return m_buf;
}
/**
* Serializes an array of values to a ByteBuffer. The returned ByteBuffer is a direct byte buffer
* with the position set to the end of the serialized data.
*
* @param values values
* @return byte buffer
*/
public ByteBuffer writeArray(T[] values) {
m_buf.position(0);
if ((values.length * m_structSize) > m_buf.capacity()) {
m_buf =
ByteBuffer.allocateDirect(values.length * m_structSize * 2)
.order(ByteOrder.LITTLE_ENDIAN);
}
for (T v : values) {
m_struct.pack(m_buf, v);
}
return m_buf;
}
/**
* Deserializes an array of values from a byte array, creating an array of new objects.
*
* @param buf byte array
* @param start starting location within byte array
* @param len length of serialized data
* @return new object array
*/
public T[] readArray(byte[] buf, int start, int len) {
return readArray(ByteBuffer.wrap(buf, start, len));
}
/**
* Deserializes an array of values from a byte array, creating an array of new objects.
*
* @param buf byte array
* @return new object array
*/
public T[] readArray(byte[] buf) {
return readArray(buf, 0, buf.length);
}
/**
* Deserializes an array of values from a ByteBuffer, creating an array of new objects.
*
* @param buf byte buffer
* @return new object array
*/
public T[] readArray(ByteBuffer buf) {
buf.order(ByteOrder.LITTLE_ENDIAN);
int len = buf.limit() - buf.position();
if ((len % m_structSize) != 0) {
throw new RuntimeException("buffer size not a multiple of struct size");
}
int nelem = len / m_structSize;
@SuppressWarnings("unchecked")
T[] arr = (T[]) Array.newInstance(m_struct.getTypeClass(), nelem);
for (int i = 0; i < nelem; i++) {
arr[i] = m_struct.unpack(buf);
}
return arr;
}
private ByteBuffer m_buf;
private final Struct<T> m_struct;
private final int m_structSize;
}

View File

@@ -0,0 +1,158 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
/** Raw struct dynamic struct descriptor. */
public class StructDescriptor {
StructDescriptor(String name) {
m_name = name;
}
/**
* Gets the struct name.
*
* @return name
*/
public String getName() {
return m_name;
}
/**
* Gets the struct schema.
*
* @return schema
*/
public String getSchema() {
return m_schema;
}
/**
* Returns whether the struct is valid (e.g. the struct is fully defined and field offsets
* computed).
*
* @return true if valid
*/
public boolean isValid() {
return m_valid;
}
/**
* Returns the struct size, in bytes. Not valid unless IsValid() is true.
*
* @return size in bytes
* @throws IllegalStateException if descriptor is invalid
*/
public int getSize() {
if (!m_valid) {
throw new IllegalStateException("descriptor is invalid");
}
return m_size;
}
/**
* Gets a field descriptor by name. Note the field cannot be accessed until the struct is valid.
*
* @param name field name
* @return field descriptor, or nullptr if not found
*/
public StructFieldDescriptor findFieldByName(String name) {
return m_fieldsByName.get(name);
}
/**
* Gets all field descriptors. Note fields cannot be accessed until the struct is valid.
*
* @return field descriptors
*/
public List<StructFieldDescriptor> getFields() {
return m_fields;
}
boolean checkCircular(Stack<StructDescriptor> stack) {
stack.push(this);
for (StructDescriptor ref : m_references) {
if (stack.contains(ref)) {
return false;
}
if (!ref.checkCircular(stack)) {
return false;
}
}
stack.pop();
return true;
}
void calculateOffsets(Stack<StructDescriptor> stack) {
int offset = 0;
int shift = 0;
int prevBitfieldSize = 0;
for (StructFieldDescriptor field : m_fields) {
if (!field.isBitField()) {
shift = 0; // reset shift on non-bitfield element
offset += prevBitfieldSize; // finish bitfield if active
prevBitfieldSize = 0; // previous is now not bitfield
field.m_offset = offset;
StructDescriptor struct = field.getStruct();
if (struct != null) {
if (!struct.isValid()) {
m_valid = false;
return;
}
field.m_size = struct.m_size;
}
offset += field.m_size * field.m_arraySize;
} else {
int bitWidth = field.getBitWidth();
if (field.getType() == StructFieldType.kBool
&& prevBitfieldSize != 0
&& (shift + 1) <= (prevBitfieldSize * 8)) {
// bool takes on size of preceding bitfield type (if it fits)
field.m_size = prevBitfieldSize;
} else if (field.m_size != prevBitfieldSize || (shift + bitWidth) > (field.m_size * 8)) {
shift = 0;
offset += prevBitfieldSize;
}
prevBitfieldSize = field.m_size;
field.m_offset = offset;
field.m_bitShift = shift;
shift += bitWidth;
}
}
// update struct size
m_size = offset + prevBitfieldSize;
m_valid = true;
// now that we're valid, referring types may be too
stack.push(this);
for (StructDescriptor ref : m_references) {
if (stack.contains(ref)) {
throw new IllegalStateException(
"internal error (inconsistent data): circular struct reference between "
+ m_name
+ " and "
+ ref.m_name);
}
ref.calculateOffsets(stack);
}
stack.pop();
}
private final String m_name;
String m_schema;
final Set<StructDescriptor> m_references = new HashSet<>();
final List<StructFieldDescriptor> m_fields = new ArrayList<>();
final Map<String, StructFieldDescriptor> m_fieldsByName = new HashMap<>();
int m_size;
boolean m_valid;
}

View File

@@ -0,0 +1,150 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
import edu.wpi.first.util.struct.parser.ParseException;
import edu.wpi.first.util.struct.parser.ParsedDeclaration;
import edu.wpi.first.util.struct.parser.ParsedSchema;
import edu.wpi.first.util.struct.parser.Parser;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
/** Database of raw struct dynamic descriptors. */
public class StructDescriptorDatabase {
/** Default constructor. */
public StructDescriptorDatabase() {}
/**
* Adds a structure schema to the database. If the struct references other structs that have not
* yet been added, it will not be valid until those structs are also added.
*
* @param name structure name
* @param schema structure schema
* @return Added struct dynamic descriptor
* @throws BadSchemaException if schema invalid
*/
public StructDescriptor add(String name, String schema) throws BadSchemaException {
Parser parser = new Parser(schema);
ParsedSchema parsed;
try {
parsed = parser.parse();
} catch (ParseException e) {
throw new BadSchemaException("parse error", e);
}
// turn parsed schema into descriptors
StructDescriptor theStruct = m_structs.computeIfAbsent(name, StructDescriptor::new);
theStruct.m_schema = schema;
theStruct.m_fields.clear();
boolean isValid = true;
for (ParsedDeclaration decl : parsed.declarations) {
StructFieldType type = StructFieldType.fromString(decl.typeString);
int size = type.size;
// bitfield checks
if (decl.bitWidth != 0) {
// only integer or boolean types are allowed
if (!type.isInt && !type.isUint && type != StructFieldType.kBool) {
throw new BadSchemaException(
decl.name, "type " + decl.typeString + " cannot be bitfield");
}
// bit width cannot be larger than field size
if (decl.bitWidth > (size * 8)) {
throw new BadSchemaException(
decl.name, "bit width " + decl.bitWidth + " exceeds type size");
}
// bit width must be 1 for booleans
if (type == StructFieldType.kBool && decl.bitWidth != 1) {
throw new BadSchemaException(decl.name, "bit width must be 1 for bool type");
}
// cannot combine array and bitfield (shouldn't parse, but double-check)
if (decl.arraySize > 1) {
throw new BadSchemaException(decl.name, "cannot combine array and bitfield");
}
}
// struct handling
StructDescriptor structDesc = null;
if (type == StructFieldType.kStruct) {
// recursive definitions are not allowed
if (decl.typeString.equals(name)) {
throw new BadSchemaException(decl.name, "recursive struct reference");
}
// cross-reference struct, creating a placeholder if necessary
StructDescriptor aStruct =
m_structs.computeIfAbsent(decl.typeString, StructDescriptor::new);
// if the struct isn't valid, we can't be valid either
if (aStruct.isValid()) {
size = aStruct.getSize();
} else {
isValid = false;
}
// add to cross-references for when the struct does become valid
aStruct.m_references.add(theStruct);
structDesc = aStruct;
}
// create field
StructFieldDescriptor fieldDesc =
new StructFieldDescriptor(
theStruct,
decl.name,
type,
size,
decl.arraySize,
decl.bitWidth,
decl.enumValues,
structDesc);
if (theStruct.m_fieldsByName.put(decl.name, fieldDesc) != null) {
throw new BadSchemaException(decl.name, "duplicate field name");
}
theStruct.m_fields.add(fieldDesc);
}
theStruct.m_valid = isValid;
Stack<StructDescriptor> stack = new Stack<>();
if (isValid) {
// we have all the info needed, so calculate field offset & shift
theStruct.calculateOffsets(stack);
} else {
// check for circular reference
if (!theStruct.checkCircular(stack)) {
StringBuilder builder = new StringBuilder("circular struct reference: ");
boolean first = true;
for (StructDescriptor elem : stack) {
if (!first) {
builder.append(" <- ");
} else {
first = false;
}
builder.append(elem.getName());
}
throw new BadSchemaException(builder.toString());
}
}
return theStruct;
}
/**
* Finds a structure in the database by name.
*
* @param name structure name
* @return struct descriptor, or null if not found
*/
public StructDescriptor find(String name) {
return m_structs.get(name);
}
private final Map<String, StructDescriptor> m_structs = new HashMap<>();
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
import java.util.Optional;
/**
* A utility class for fetching the assigned struct of existing classes. These are usually public,
* static, and final properties with the Struct type.
*/
public final class StructFetcher {
private StructFetcher() {
throw new UnsupportedOperationException("This is a utility class!");
}
/**
* Returns a {@link Struct} for the given {@link StructSerializable} marked class. Due to the
* non-contractual nature of the marker this can fail. If the {@code struct} field could not be
* accessed for any reason, an empty {@link Optional} is returned.
*
* @param <T> The type of the class.
* @param clazz The class object to extract the struct from.
* @return An optional containing the struct if it could be extracted.
*/
@SuppressWarnings("unchecked")
public static <T extends StructSerializable> Optional<Struct<T>> fetchStruct(
Class<? extends T> clazz) {
try {
var possibleField = Optional.ofNullable(clazz.getDeclaredField("struct"));
return possibleField.flatMap(
field -> {
if (Struct.class.isAssignableFrom(field.getType())) {
try {
return Optional.ofNullable((Struct<T>) field.get(null));
} catch (IllegalAccessException e) {
return Optional.empty();
}
} else {
return Optional.empty();
}
});
} catch (NoSuchFieldException e) {
return Optional.empty();
}
}
/**
* Returns a {@link Struct} for the given class. This does not do compile time checking that the
* class is a {@link StructSerializable}. Whenever possible it is reccomended to use {@link
* #fetchStruct(Class)}.
*
* @param clazz The class object to extract the struct from.
* @return An optional containing the struct if it could be extracted.
*/
@SuppressWarnings("unchecked")
public static Optional<Struct<?>> fetchStructDynamic(Class<?> clazz) {
if (StructSerializable.class.isAssignableFrom(clazz)) {
return fetchStruct((Class<? extends StructSerializable>) clazz).map(struct -> struct);
} else {
return Optional.empty();
}
}
}

View File

@@ -0,0 +1,241 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
import java.util.Map;
/** Raw struct dynamic field descriptor. */
public class StructFieldDescriptor {
private static int toBitWidth(int size, int bitWidth) {
if (bitWidth == 0) {
return size * 8;
} else {
return bitWidth;
}
}
private static long toBitMask(int size, int bitWidth) {
if (size == 0) {
return 0;
} else {
return -1L >>> (64 - toBitWidth(size, bitWidth));
}
}
// does not fill in offset, shift
StructFieldDescriptor(
StructDescriptor parent,
String name,
StructFieldType type,
int size,
int arraySize,
int bitWidth,
Map<String, Long> enumValues,
StructDescriptor structDesc) {
m_parent = parent;
m_name = name;
m_size = size;
m_arraySize = arraySize;
m_enum = enumValues;
m_struct = structDesc;
m_bitMask = toBitMask(size, bitWidth);
m_type = type;
m_bitWidth = toBitWidth(size, bitWidth);
}
/**
* Gets the dynamic struct this field is contained in.
*
* @return struct descriptor
*/
public StructDescriptor getParent() {
return m_parent;
}
/**
* Gets the field name.
*
* @return field name
*/
public String getName() {
return m_name;
}
/**
* Gets the field type.
*
* @return field type
*/
public StructFieldType getType() {
return m_type;
}
/**
* Returns whether the field type is a signed integer.
*
* @return true if signed integer, false otherwise
*/
public boolean isInt() {
return m_type.isInt;
}
/**
* Returns whether the field type is an unsigned integer.
*
* @return true if unsigned integer, false otherwise
*/
public boolean isUint() {
return m_type.isUint;
}
/**
* Gets the underlying storage size of the field, in bytes.
*
* @return number of bytes
*/
public int getSize() {
return m_size;
}
/**
* Gets the storage offset of the field, in bytes.
*
* @return number of bytes from the start of the struct
*/
public int getOffset() {
return m_offset;
}
/**
* Gets the bit width of the field, in bits.
*
* @return number of bits
*/
public int getBitWidth() {
return m_bitWidth == 0 ? m_size * 8 : m_bitWidth;
}
/**
* Gets the bit mask for the field. The mask is always the least significant bits (it is not
* shifted).
*
* @return bit mask
*/
public long getBitMask() {
return m_bitMask;
}
/**
* Gets the bit shift for the field (LSB=0).
*
* @return number of bits
*/
public int getBitShift() {
return m_bitShift;
}
/**
* Returns whether the field is an array.
*
* @return true if array
*/
public boolean isArray() {
return m_arraySize > 1;
}
/**
* Gets the array size. Returns 1 if non-array.
*
* @return number of elements
*/
public int getArraySize() {
return m_arraySize;
}
/**
* Returns whether the field has enumerated values.
*
* @return true if there are enumerated values
*/
public boolean hasEnum() {
return m_enum != null;
}
/**
* Gets the enumerated values.
*
* @return set of enumerated values
*/
public Map<String, Long> getEnumValues() {
return m_enum;
}
/**
* Gets the struct descriptor for a struct data type.
*
* @return struct descriptor; returns null for non-struct
*/
public StructDescriptor getStruct() {
return m_struct;
}
/**
* Gets the minimum unsigned integer value that can be stored in this field.
*
* @return minimum value
*/
public long getUintMin() {
return 0;
}
/**
* Gets the maximum unsigned integer value that can be stored in this field. Note this is not the
* actual maximum for uint64 (due to Java lacking support for 64-bit unsigned integers).
*
* @return maximum value
*/
public long getUintMax() {
return m_bitMask;
}
/**
* Gets the minimum signed integer value that can be stored in this field.
*
* @return minimum value
*/
public long getIntMin() {
return -(m_bitMask >> 1) - 1;
}
/**
* Gets the maximum signed integer value that can be stored in this field.
*
* @return maximum value
*/
public long getIntMax() {
return m_bitMask >> 1;
}
/**
* Returns whether the field is a bitfield.
*
* @return true if bitfield
*/
public boolean isBitField() {
return (m_bitShift != 0 || m_bitWidth != (m_size * 8)) && m_struct == null;
}
private final StructDescriptor m_parent;
private final String m_name;
int m_size;
int m_offset;
final int m_arraySize; // 1 for non-arrays
private final Map<String, Long> m_enum;
private final StructDescriptor m_struct; // null for non-structs
private final long m_bitMask;
private final StructFieldType m_type;
private final int m_bitWidth;
int m_bitShift;
}

View File

@@ -0,0 +1,84 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
/** Known data types for raw struct dynamic fields (see StructFieldDescriptor). */
public enum StructFieldType {
/** bool. */
kBool("bool", false, false, 1),
/** char. */
kChar("char", false, false, 1),
/** int8. */
kInt8("int8", true, false, 1),
/** int16. */
kInt16("int16", true, false, 2),
/** int32. */
kInt32("int32", true, false, 4),
/** int64. */
kInt64("int64", true, false, 8),
/** uint8. */
kUint8("uint8", false, true, 1),
/** uint16. */
kUint16("uint16", false, true, 2),
/** uint32. */
kUint32("uint32", false, true, 4),
/** uint64. */
kUint64("uint64", false, true, 8),
/** float. */
kFloat("float", false, false, 4),
/** double. */
kDouble("double", false, false, 8),
/** struct. */
kStruct("struct", false, false, 0);
/** The name of the data type. */
@SuppressWarnings("MemberName")
public final String name;
/** Indicates if the data type is a signed integer. */
@SuppressWarnings("MemberName")
public final boolean isInt;
/** Indicates if the data type is an unsigned integer. */
@SuppressWarnings("MemberName")
public final boolean isUint;
/** The size (in bytes) of the data type. */
@SuppressWarnings("MemberName")
public final int size;
StructFieldType(String name, boolean isInt, boolean isUint, int size) {
this.name = name;
this.isInt = isInt;
this.isUint = isUint;
this.size = size;
}
@Override
public String toString() {
return name;
}
/**
* Get field type from string.
*
* @param str string
* @return field type
*/
public static StructFieldType fromString(String str) {
for (StructFieldType type : StructFieldType.values()) {
if (type.name.equals(str)) {
return type;
}
}
if ("float32".equals(str)) {
return kFloat;
} else if ("float64".equals(str)) {
return kDouble;
} else {
return kStruct;
}
}
}

View File

@@ -0,0 +1,573 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.RecordComponent;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/** A utility class for procedurally generating {@link Struct}s from records and enums. */
public final class StructGenerator {
private StructGenerator() {
throw new UnsupportedOperationException("This is a utility class!");
}
/**
* A functional interface representing a method that retrives a value from a {@link ByteBuffer}.
*/
@FunctionalInterface
private interface Unpacker<T> {
T unpack(ByteBuffer buffer);
}
/** A functional interface representing a method that packs a value into a {@link ByteBuffer}. */
@FunctionalInterface
private interface Packer<T> {
ByteBuffer pack(ByteBuffer buffer, T value);
static <T> Packer<T> fromStruct(Struct<T> struct) {
return (buffer, value) -> {
struct.pack(buffer, value);
return buffer;
};
}
}
private record PrimType<T>(String name, int size, Unpacker<T> unpacker, Packer<T> packer) {}
/** A map of primitive types to their schema types. */
private static final HashMap<Class<?>, PrimType<?>> primitiveTypeMap = new HashMap<>();
private static <T> void addPrimType(
Class<T> boxedClass,
Class<T> primitiveClass,
String name,
int size,
Unpacker<T> unpacker,
Packer<T> packer) {
PrimType<T> primType = new PrimType<>(name, size, unpacker, packer);
primitiveTypeMap.put(boxedClass, primType);
primitiveTypeMap.put(primitiveClass, primType);
}
// Add primitive types to the map
static {
addPrimType(
Integer.class, int.class, "int32", Integer.BYTES, ByteBuffer::getInt, ByteBuffer::putInt);
addPrimType(
Double.class,
double.class,
"float64",
Double.BYTES,
ByteBuffer::getDouble,
ByteBuffer::putDouble);
addPrimType(
Float.class,
float.class,
"float32",
Float.BYTES,
ByteBuffer::getFloat,
ByteBuffer::putFloat);
addPrimType(
Boolean.class,
boolean.class,
"bool",
Byte.BYTES,
buffer -> buffer.get() != 0,
(buffer, value) -> buffer.put((byte) (value ? 1 : 0)));
addPrimType(
Character.class,
char.class,
"char",
Character.BYTES,
ByteBuffer::getChar,
ByteBuffer::putChar);
addPrimType(Byte.class, byte.class, "uint8", Byte.BYTES, ByteBuffer::get, ByteBuffer::put);
addPrimType(
Short.class, short.class, "int16", Short.BYTES, ByteBuffer::getShort, ByteBuffer::putShort);
addPrimType(
Long.class, long.class, "int64", Long.BYTES, ByteBuffer::getLong, ByteBuffer::putLong);
}
/**
* A map of types to their custom struct schemas.
*
* <p>This allows adding custom struct implementations for types that are not supported by
* default. Think of vendor-specific.
*/
private static final HashMap<Class<?>, Struct<?>> customStructTypeMap = new HashMap<>();
/**
* Add a custom struct to the structifier.
*
* @param <T> The type the struct is for.
* @param clazz The class of the type.
* @param struct The struct to add.
* @param override Whether to override an existing struct. An existing struct could mean the type
* already has a {@code struct} field and implemnts {@link StructSerializable} or that the
* type is already in the custom struct map.
*/
public static <T> void addCustomStruct(Class<T> clazz, Struct<T> struct, boolean override) {
if (override) {
customStructTypeMap.put(clazz, struct);
} else if (!StructSerializable.class.isAssignableFrom(clazz)) {
customStructTypeMap.putIfAbsent(clazz, struct);
}
}
/** A utility for building schema syntax in a procedural manner. */
@SuppressWarnings("PMD.AvoidStringBufferField")
public static class SchemaBuilder {
/** A utility for building enum fields in a procedural manner. */
public static final class EnumFieldBuilder {
private final StringBuilder m_builder = new StringBuilder();
private final String m_fieldName;
private boolean m_firstVariant = true;
/**
* Creates a new enum field builder.
*
* @param fieldName The name of the field.
*/
public EnumFieldBuilder(String fieldName) {
this.m_fieldName = fieldName;
m_builder.append("enum {");
}
/**
* Adds a variant to the enum field.
*
* @param name The name of the variant.
* @param value The value of the variant.
* @return The builder for chaining.
*/
public EnumFieldBuilder addVariant(String name, int value) {
if (!m_firstVariant) {
m_builder.append(',');
}
m_firstVariant = false;
m_builder.append(name).append('=').append(value);
return this;
}
/**
* Builds the enum field. If this object is being used with {@link SchemaBuilder#addEnumField}
* then {@link #build()} does not have to be called by the user.
*
* @return The built enum field.
*/
public String build() {
m_builder.append("} int8 ").append(m_fieldName).append(';');
return m_builder.toString();
}
}
/** Creates a new schema builder. */
public SchemaBuilder() {}
private final StringBuilder m_builder = new StringBuilder();
/**
* Adds a field to the schema.
*
* @param name The name of the field.
* @param type The type of the field.
* @return The builder for chaining.
*/
public SchemaBuilder addField(String name, String type) {
m_builder.append(type).append(' ').append(name).append(';');
return this;
}
/**
* Adds an inline enum field to the schema.
*
* @param enumFieldBuilder The builder for the enum field.
* @return The builder for chaining.
*/
public SchemaBuilder addEnumField(EnumFieldBuilder enumFieldBuilder) {
m_builder.append(enumFieldBuilder.build());
return this;
}
/**
* Builds the schema.
*
* @return The built schema.
*/
public String build() {
return m_builder.toString();
}
}
private static <T> Struct<T> noopStruct(Class<T> cls) {
return new Struct<>() {
@Override
public Class<T> getTypeClass() {
return cls;
}
@Override
public String getTypeName() {
return cls.getSimpleName();
}
@Override
public String getSchema() {
return "";
}
@Override
public int getSize() {
return 0;
}
@Override
public void pack(ByteBuffer buffer, T value) {}
@Override
public T unpack(ByteBuffer buffer) {
return null;
}
@Override
public boolean isImmutable() {
return true;
}
};
}
/**
* Generates a {@link Struct} for the given {@link Record} class. If a {@link Struct} cannot be
* generated from the {@link Record}, the errors encountered will be printed and a no-op {@link
* Struct} will be returned.
*
* @param <R> The type of the record.
* @param recordClass The class of the record.
* @return The generated struct.
*/
@SuppressWarnings({"unchecked", "PMD.AvoidAccessibilityAlteration"})
public static <R extends Record> Struct<R> genRecord(final Class<R> recordClass) {
final RecordComponent[] components = recordClass.getRecordComponents();
final SchemaBuilder schemaBuilder = new SchemaBuilder();
final ArrayList<Struct<?>> nestedStructs = new ArrayList<>();
final ArrayList<Unpacker<?>> unpackers = new ArrayList<>();
final ArrayList<Packer<?>> packers = new ArrayList<>();
int size = 0;
boolean failed = false;
for (final RecordComponent component : components) {
final Class<?> type = component.getType();
final String name = component.getName();
component.getAccessor().setAccessible(true);
if (primitiveTypeMap.containsKey(type)) {
PrimType<?> primType = primitiveTypeMap.get(type);
schemaBuilder.addField(name, primType.name);
size += primType.size;
unpackers.add(primType.unpacker);
packers.add(primType.packer);
} else {
Struct<?> struct;
if (customStructTypeMap.containsKey(type)) {
struct = customStructTypeMap.get(type);
} else if (StructSerializable.class.isAssignableFrom(type)) {
var optStruct = StructFetcher.fetchStructDynamic(type);
if (optStruct.isPresent()) {
struct = optStruct.get();
} else {
System.err.println(
"Could not structify record component: "
+ recordClass.getSimpleName()
+ "#"
+ name
+ "\n Could not extract struct from marked class: "
+ type.getName());
failed = true;
continue;
}
} else {
System.err.println(
"Could not structify record component: " + recordClass.getSimpleName() + "#" + name);
failed = true;
continue;
}
schemaBuilder.addField(name, struct.getTypeName());
size += struct.getSize();
nestedStructs.add(struct);
nestedStructs.addAll(List.of(struct.getNested()));
unpackers.add(struct::unpack);
packers.add(Packer.fromStruct(struct));
}
}
if (failed) {
return noopStruct(recordClass);
}
final int frozenSize = size;
final String schema = schemaBuilder.build();
return new Struct<>() {
@Override
public Class<R> getTypeClass() {
return recordClass;
}
@Override
public String getTypeName() {
return recordClass.getSimpleName();
}
@Override
public String getSchema() {
return schema;
}
@Override
public int getSize() {
return frozenSize;
}
@Override
public void pack(ByteBuffer buffer, R value) {
boolean failed = false;
int startingPosition = buffer.position();
for (int i = 0; i < components.length; i++) {
Packer<Object> packer = (Packer<Object>) packers.get(i);
try {
Object componentValue = components[i].getAccessor().invoke(value);
if (componentValue == null) {
throw new IllegalArgumentException("Component is null");
}
packer.pack(buffer, componentValue);
} catch (IllegalAccessException
| IllegalArgumentException
| InvocationTargetException e) {
System.err.println(
"Could not pack record component: "
+ recordClass.getSimpleName()
+ "#"
+ components[i].getName()
+ "\n "
+ e.getMessage());
failed = true;
break;
}
}
if (failed) {
buffer.position(startingPosition);
for (int i = 0; i < frozenSize; i++) {
buffer.put((byte) 0);
}
}
}
@Override
public R unpack(ByteBuffer buffer) {
try {
Object[] args = new Object[components.length];
Class<?>[] argTypes = new Class<?>[components.length];
for (int i = 0; i < components.length; i++) {
args[i] = unpackers.get(i).unpack(buffer);
argTypes[i] = components[i].getType();
}
return recordClass.getConstructor(argTypes).newInstance(args);
} catch (InstantiationException
| IllegalAccessException
| InvocationTargetException
| NoSuchMethodException
| SecurityException e) {
System.err.println(
"Could not unpack record: "
+ recordClass.getSimpleName()
+ "\n "
+ e.getMessage());
return null;
}
}
@Override
public Struct<?>[] getNested() {
return nestedStructs.toArray(new Struct<?>[0]);
}
@Override
public boolean isImmutable() {
return true;
}
};
}
/**
* Generates a {@link Struct} for the given {@link Enum} class. If a {@link Struct} cannot be
* generated from the {@link Enum}, the errors encountered will be printed and a no-op {@link
* Struct} will be returned.
*
* @param <E> The type of the enum.
* @param enumClass The class of the enum.
* @return The generated struct.
*/
@SuppressWarnings({"unchecked", "PMD.AvoidAccessibilityAlteration"})
public static <E extends Enum<E>> Struct<E> genEnum(Class<E> enumClass) {
final E[] enumVariants = enumClass.getEnumConstants();
final Field[] allEnumFields = enumClass.getDeclaredFields();
final SchemaBuilder schemaBuilder = new SchemaBuilder();
final SchemaBuilder.EnumFieldBuilder enumFieldBuilder =
new SchemaBuilder.EnumFieldBuilder("variant");
final HashMap<Integer, E> enumMap = new HashMap<>();
final ArrayList<Packer<?>> packers = new ArrayList<>();
if (enumVariants == null || enumVariants.length == 0) {
System.err.println(
"Could not structify enum: "
+ enumClass.getSimpleName()
+ "\n "
+ "Enum has no constants");
return noopStruct(enumClass);
}
int size = 0;
for (final E constant : enumVariants) {
final String name = constant.name();
final int ordinal = constant.ordinal();
enumFieldBuilder.addVariant(name, ordinal);
enumMap.put(ordinal, constant);
}
schemaBuilder.addEnumField(enumFieldBuilder);
size += 1;
final List<Field> enumFields =
List.of(allEnumFields).stream()
.filter(f -> !f.isEnumConstant() && !Modifier.isStatic(f.getModifiers()))
.toList();
boolean failed = false;
for (final Field field : enumFields) {
final Class<?> type = field.getType();
final String name = field.getName();
field.setAccessible(true);
if (primitiveTypeMap.containsKey(type)) {
PrimType<?> primType = primitiveTypeMap.get(type);
schemaBuilder.addField(name, primType.name);
size += primType.size;
packers.add(primType.packer);
} else {
Struct<?> struct;
if (customStructTypeMap.containsKey(type)) {
struct = customStructTypeMap.get(type);
} else if (StructSerializable.class.isAssignableFrom(type)) {
var optStruct = StructFetcher.fetchStructDynamic(type);
if (optStruct.isPresent()) {
struct = optStruct.get();
} else {
System.err.println(
"Could not structify record component: "
+ enumClass.getSimpleName()
+ "#"
+ name
+ "\n Could not extract struct from marked class: "
+ type.getName());
failed = true;
continue;
}
} else {
System.err.println(
"Could not structify record component: " + enumClass.getSimpleName() + "#" + name);
failed = true;
continue;
}
schemaBuilder.addField(name, struct.getTypeName());
size += struct.getSize();
packers.add(Packer.fromStruct(struct));
}
}
if (failed) {
return noopStruct(enumClass);
}
final int frozenSize = size;
final String schema = schemaBuilder.build();
return new Struct<>() {
@Override
public Class<E> getTypeClass() {
return enumClass;
}
@Override
public String getTypeName() {
return enumClass.getSimpleName();
}
@Override
public String getSchema() {
return schema;
}
@Override
public int getSize() {
return frozenSize;
}
@Override
public void pack(ByteBuffer buffer, E value) {
boolean failed = false;
int startingPosition = buffer.position();
buffer.put((byte) value.ordinal());
for (int i = 0; i < enumFields.size(); i++) {
Packer<Object> packer = (Packer<Object>) packers.get(i);
Field field = enumFields.get(i);
try {
Object fieldValue = field.get(value);
if (fieldValue == null) {
throw new IllegalArgumentException("Field is null");
}
packer.pack(buffer, fieldValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
System.err.println(
"Could not pack enum field: "
+ enumClass.getSimpleName()
+ "#"
+ field.getName()
+ "\n "
+ e.getMessage());
failed = true;
break;
}
}
if (failed) {
buffer.position(startingPosition);
for (int i = 0; i < frozenSize; i++) {
buffer.put((byte) 0);
}
}
}
final byte[] m_spongeBuffer = new byte[frozenSize - 1];
@Override
public E unpack(ByteBuffer buffer) {
int ordinal = buffer.get();
buffer.get(m_spongeBuffer);
return enumMap.getOrDefault(ordinal, null);
}
@Override
public boolean isImmutable() {
return true;
}
};
}
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct;
import edu.wpi.first.util.WPISerializable;
/**
* Marker interface to indicate a class is serializable using Struct serialization.
*
* <p>While this cannot be enforced by the interface, any class implementing this interface should
* provide a public final static `struct` member variable, or a static final `getStruct()` method if
* the class is generic.
*/
public interface StructSerializable extends WPISerializable {}

View File

@@ -0,0 +1,113 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct.parser;
/** Raw struct schema lexer. */
public class Lexer {
/**
* Construct a raw struct schema lexer.
*
* @param in schema
*/
public Lexer(String in) {
m_in = in;
}
/**
* Gets the next token.
*
* @return Token kind; the token text can be retrieved using getTokenText()
*/
public TokenKind scan() {
// skip whitespace
do {
get();
} while (m_current == ' ' || m_current == '\t' || m_current == '\n' || m_current == '\r');
m_tokenStart = m_pos - 1;
return switch (m_current) {
case '[' -> TokenKind.kLeftBracket;
case ']' -> TokenKind.kRightBracket;
case '{' -> TokenKind.kLeftBrace;
case '}' -> TokenKind.kRightBrace;
case ':' -> TokenKind.kColon;
case ';' -> TokenKind.kSemicolon;
case ',' -> TokenKind.kComma;
case '=' -> TokenKind.kEquals;
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> scanInteger();
case '\0' -> TokenKind.kEndOfInput;
default -> {
if (Character.isLetter(m_current) || m_current == '_') {
yield scanIdentifier();
}
yield TokenKind.kUnknown;
}
};
}
/**
* Gets the text of the last lexed token.
*
* @return token text
*/
public String getTokenText() {
if (m_tokenStart >= m_in.length()) {
return "";
}
return m_in.substring(m_tokenStart, m_pos);
}
/**
* Gets the starting position of the last lexed token.
*
* @return position (0 = first character)
*/
public int getPosition() {
return m_tokenStart;
}
private TokenKind scanInteger() {
do {
get();
} while (Character.isDigit(m_current));
unget();
return TokenKind.kInteger;
}
private TokenKind scanIdentifier() {
do {
get();
} while (Character.isLetterOrDigit(m_current) || m_current == '_');
unget();
return TokenKind.kIdentifier;
}
private void get() {
if (m_pos < m_in.length()) {
m_current = m_in.charAt(m_pos);
} else {
m_current = '\0';
}
++m_pos;
}
private void unget() {
if (m_pos > 0) {
m_pos--;
if (m_pos < m_in.length()) {
m_current = m_in.charAt(m_pos);
} else {
m_current = '\0';
}
} else {
m_current = '\0';
}
}
final String m_in;
char m_current;
int m_tokenStart;
int m_pos;
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct.parser;
/** Exception for parsing errors. */
public class ParseException extends Exception {
/** The parser position. */
private final int m_pos;
/**
* Constructs a ParseException.
*
* @param pos The parser position.
* @param s Reason for parse failure.
*/
public ParseException(int pos, String s) {
super(s);
m_pos = pos;
}
/**
* Constructs a ParseException.
*
* @param pos The parser position.
* @param message Reason for parse failure.
* @param cause Exception that caused the parser failure.
*/
public ParseException(int pos, String message, Throwable cause) {
super(message, cause);
m_pos = pos;
}
/**
* Constructs a ParseException.
*
* @param pos The parser position.
* @param cause Exception that caused the parser failure.
*/
public ParseException(int pos, Throwable cause) {
super(cause);
m_pos = pos;
}
/**
* Returns position in parsed string.
*
* @return Position in parsed string.
*/
public int getPosition() {
return m_pos;
}
@Override
public String getMessage() {
return m_pos + ": " + super.getMessage();
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct.parser;
import java.util.Map;
/** Raw struct schema declaration. */
public class ParsedDeclaration {
/** Type string. */
@SuppressWarnings("MemberName")
public String typeString;
/** Name. */
@SuppressWarnings("MemberName")
public String name;
/** Enum values. */
@SuppressWarnings("MemberName")
public Map<String, Long> enumValues;
/** Array size. */
@SuppressWarnings("MemberName")
public int arraySize = 1;
/** Bit width. */
@SuppressWarnings("MemberName")
public int bitWidth;
/** Default constructor. */
public ParsedDeclaration() {}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct.parser;
import java.util.ArrayList;
import java.util.List;
/** Raw struct schema. */
public class ParsedSchema {
/** Declarations. */
@SuppressWarnings("MemberName")
public List<ParsedDeclaration> declarations = new ArrayList<>();
/** Default constructor. */
public ParsedSchema() {}
}

View File

@@ -0,0 +1,157 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct.parser;
import java.util.HashMap;
import java.util.Map;
/** Raw struct schema parser. */
public class Parser {
/**
* Construct a raw struct schema parser.
*
* @param in schema
*/
public Parser(String in) {
m_lexer = new Lexer(in);
}
/**
* Parses the schema.
*
* @return parsed schema object
* @throws ParseException on parse error
*/
public ParsedSchema parse() throws ParseException {
ParsedSchema schema = new ParsedSchema();
do {
getNextToken();
if (m_token == TokenKind.kSemicolon) {
continue;
}
if (m_token == TokenKind.kEndOfInput) {
break;
}
schema.declarations.add(parseDeclaration());
} while (m_token != TokenKind.kEndOfInput);
return schema;
}
private ParsedDeclaration parseDeclaration() throws ParseException {
ParsedDeclaration decl = new ParsedDeclaration();
// optional enum specification
if (m_token == TokenKind.kIdentifier && "enum".equals(m_lexer.getTokenText())) {
getNextToken();
expect(TokenKind.kLeftBrace);
decl.enumValues = parseEnum();
getNextToken();
} else if (m_token == TokenKind.kLeftBrace) {
decl.enumValues = parseEnum();
getNextToken();
}
// type name
expect(TokenKind.kIdentifier);
decl.typeString = m_lexer.getTokenText();
getNextToken();
// identifier name
expect(TokenKind.kIdentifier);
decl.name = m_lexer.getTokenText();
getNextToken();
// array or bit field
if (m_token == TokenKind.kLeftBracket) {
getNextToken();
expect(TokenKind.kInteger);
String valueStr = m_lexer.getTokenText();
int value;
try {
value = Integer.parseInt(valueStr);
} catch (NumberFormatException e) {
value = 0;
}
if (value > 0) {
decl.arraySize = value;
} else {
throw new ParseException(
m_lexer.m_pos, "array size '" + valueStr + "' is not a positive integer");
}
getNextToken();
expect(TokenKind.kRightBracket);
getNextToken();
} else if (m_token == TokenKind.kColon) {
getNextToken();
expect(TokenKind.kInteger);
String valueStr = m_lexer.getTokenText();
int value;
try {
value = Integer.parseInt(valueStr);
} catch (NumberFormatException e) {
value = 0;
}
if (value > 0) {
decl.bitWidth = value;
} else {
throw new ParseException(
m_lexer.m_pos, "bitfield width '" + valueStr + "' is not a positive integer");
}
getNextToken();
}
// declaration must end with EOF or semicolon
if (m_token != TokenKind.kEndOfInput) {
expect(TokenKind.kSemicolon);
}
return decl;
}
private Map<String, Long> parseEnum() throws ParseException {
Map<String, Long> map = new HashMap<>();
// we start with current = '{'
getNextToken();
while (m_token != TokenKind.kRightBrace) {
expect(TokenKind.kIdentifier);
final String name = m_lexer.getTokenText();
getNextToken();
expect(TokenKind.kEquals);
getNextToken();
expect(TokenKind.kInteger);
String valueStr = m_lexer.getTokenText();
long value;
try {
value = Long.parseLong(valueStr);
} catch (NumberFormatException e) {
throw new ParseException(m_lexer.m_pos, "could not parse enum value '" + valueStr + "'");
}
map.put(name, value);
getNextToken();
if (m_token == TokenKind.kRightBrace) {
break;
}
expect(TokenKind.kComma);
getNextToken();
}
return map;
}
private TokenKind getNextToken() {
m_token = m_lexer.scan();
return m_token;
}
private void expect(TokenKind kind) throws ParseException {
if (m_token != kind) {
throw new ParseException(
m_lexer.m_pos, "expected " + kind + ", got '" + m_lexer.getTokenText() + "'");
}
}
final Lexer m_lexer;
TokenKind m_token;
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util.struct.parser;
/** A lexed raw struct schema token. */
public enum TokenKind {
/** Unknown. */
kUnknown("unknown"),
/** Integer. */
kInteger("integer"),
/** Identifier. */
kIdentifier("identifier"),
/** Left square bracket. */
kLeftBracket("'['"),
/** Right square bracket. */
kRightBracket("']'"),
/** Left curly brace. */
kLeftBrace("'{'"),
/** Right curly brace. */
kRightBrace("'}'"),
/** Colon. */
kColon("':'"),
/** Semicolon. */
kSemicolon("';'"),
/** Comma. */
kComma("','"),
/** Equals. */
kEquals("'='"),
/** End of input. */
kEndOfInput("<EOF>");
private final String m_name;
TokenKind(String name) {
this.m_name = name;
}
@Override
public String toString() {
return m_name;
}
}