mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-22 01:11:42 +00:00
SCRIPT Move java files
This commit is contained in:
committed by
Peter Johnson
parent
7ca1be9bae
commit
c350c5f112
@@ -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();
|
||||
}
|
||||
}
|
||||
658
wpiutil/src/main/java/org/wpilib/util/struct/DynamicStruct.java
Normal file
658
wpiutil/src/main/java/org/wpilib/util/struct/DynamicStruct.java
Normal 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;
|
||||
}
|
||||
227
wpiutil/src/main/java/org/wpilib/util/struct/Struct.java
Normal file
227
wpiutil/src/main/java/org/wpilib/util/struct/Struct.java
Normal 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();
|
||||
}
|
||||
}
|
||||
231
wpiutil/src/main/java/org/wpilib/util/struct/StructBuffer.java
Normal file
231
wpiutil/src/main/java/org/wpilib/util/struct/StructBuffer.java
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<>();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
113
wpiutil/src/main/java/org/wpilib/util/struct/parser/Lexer.java
Normal file
113
wpiutil/src/main/java/org/wpilib/util/struct/parser/Lexer.java
Normal 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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
157
wpiutil/src/main/java/org/wpilib/util/struct/parser/Parser.java
Normal file
157
wpiutil/src/main/java/org/wpilib/util/struct/parser/Parser.java
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user