mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-26 01:51:41 +00:00
SCRIPT Move java files
This commit is contained in:
committed by
Peter Johnson
parent
7ca1be9bae
commit
c350c5f112
41
wpiutil/src/main/java/org/wpilib/util/ErrorMessages.java
Normal file
41
wpiutil/src/main/java/org/wpilib/util/ErrorMessages.java
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/** Utility class for common WPILib error messages. */
|
||||
public final class ErrorMessages {
|
||||
/** Utility class, so constructor is private. */
|
||||
private ErrorMessages() {
|
||||
throw new UnsupportedOperationException("This is a utility class!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires that a parameter of a method not be null; prints an error message with helpful
|
||||
* debugging instructions if the parameter is null.
|
||||
*
|
||||
* @param <T> Type of object.
|
||||
* @param obj The parameter that must not be null.
|
||||
* @param paramName The name of the parameter.
|
||||
* @param methodName The name of the method.
|
||||
* @return The object parameter confirmed not to be null.
|
||||
*/
|
||||
public static <T> T requireNonNullParam(T obj, String paramName, String methodName) {
|
||||
return requireNonNull(
|
||||
obj,
|
||||
"Parameter "
|
||||
+ paramName
|
||||
+ " in method "
|
||||
+ methodName
|
||||
+ " was null when it"
|
||||
+ " should not have been! Check the stacktrace to find the responsible line of code - "
|
||||
+ "usually, it is the first line of user-written code indicated in the stacktrace. "
|
||||
+ "Make sure all objects passed to the method in question were properly initialized -"
|
||||
+ " note that this may not be obvious if it is being called under "
|
||||
+ "dynamically-changing conditions! Please do not seek additional technical assistance"
|
||||
+ " without doing this first!");
|
||||
}
|
||||
}
|
||||
54
wpiutil/src/main/java/org/wpilib/util/PixelFormat.java
Normal file
54
wpiutil/src/main/java/org/wpilib/util/PixelFormat.java
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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;
|
||||
|
||||
/** Image pixel format. */
|
||||
public enum PixelFormat {
|
||||
/** Unknown format. */
|
||||
kUnknown(0),
|
||||
/** Motion-JPEG (compressed image data). */
|
||||
kMJPEG(1),
|
||||
/** YUY 4:2:2, 16 bpp. */
|
||||
kYUYV(2),
|
||||
/** RGB 5-6-5, 16 bpp. */
|
||||
kRGB565(3),
|
||||
/** BGR 8-8-8, 24 bpp. */
|
||||
kBGR(4),
|
||||
/** Grayscale, 8 bpp. */
|
||||
kGray(5),
|
||||
/** Grayscale, 16 bpp. */
|
||||
kY16(6),
|
||||
/** YUV 4:2:2, 16 bpp. */
|
||||
kUYVY(7),
|
||||
/** BGRA 8-8-8-8. 32 bpp. */
|
||||
kBGRA(8);
|
||||
|
||||
private final int value;
|
||||
|
||||
PixelFormat(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the integer value of the pixel format.
|
||||
*
|
||||
* @return Integer value
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static final PixelFormat[] s_values = values();
|
||||
|
||||
/**
|
||||
* Gets a PixelFormat enum value from its integer value.
|
||||
*
|
||||
* @param pixelFormat integer value
|
||||
* @return Enum value
|
||||
*/
|
||||
public static PixelFormat getFromInt(int pixelFormat) {
|
||||
return s_values[pixelFormat];
|
||||
}
|
||||
}
|
||||
226
wpiutil/src/main/java/org/wpilib/util/RawFrame.java
Normal file
226
wpiutil/src/main/java/org/wpilib/util/RawFrame.java
Normal file
@@ -0,0 +1,226 @@
|
||||
// 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;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Class for storing raw frame data between image read call.
|
||||
*
|
||||
* <p>Data is reused for each frame read, rather then reallocating every frame.
|
||||
*/
|
||||
public class RawFrame implements AutoCloseable {
|
||||
private long m_nativeObj;
|
||||
private ByteBuffer m_data;
|
||||
private int m_width;
|
||||
private int m_height;
|
||||
private int m_stride;
|
||||
private PixelFormat m_pixelFormat = PixelFormat.kUnknown;
|
||||
private long m_time;
|
||||
private TimestampSource m_timeSource = TimestampSource.kUnknown;
|
||||
|
||||
/** Construct a new empty RawFrame. */
|
||||
public RawFrame() {
|
||||
m_nativeObj = WPIUtilJNI.allocateRawFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the RawFrame, releasing native resources. Any images currently using the data will be
|
||||
* invalidated.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
WPIUtilJNI.freeRawFrame(m_nativeObj);
|
||||
m_nativeObj = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from JNI to set data in class.
|
||||
*
|
||||
* @param data A native ByteBuffer pointing to the frame data.
|
||||
* @param width The width of the frame, in pixels
|
||||
* @param height The height of the frame, in pixels
|
||||
* @param stride The number of bytes in each row of image data
|
||||
* @param pixelFormat The PixelFormat of the frame
|
||||
*/
|
||||
void setDataJNI(
|
||||
ByteBuffer data, int width, int height, int stride, int pixelFormat, long time, int timeSrc) {
|
||||
m_data = data;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_stride = stride;
|
||||
m_pixelFormat = PixelFormat.getFromInt(pixelFormat);
|
||||
m_time = time;
|
||||
m_timeSource = TimestampSource.getFromInt(timeSrc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from JNI to set info in class.
|
||||
*
|
||||
* @param width The width of the frame, in pixels
|
||||
* @param height The height of the frame, in pixels
|
||||
* @param stride The number of bytes in each row of image data
|
||||
* @param pixelFormat The PixelFormat of the frame
|
||||
*/
|
||||
void setInfoJNI(int width, int height, int stride, int pixelFormat, long time, int timeSrc) {
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_stride = stride;
|
||||
m_pixelFormat = PixelFormat.getFromInt(pixelFormat);
|
||||
m_time = time;
|
||||
m_timeSource = TimestampSource.getFromInt(timeSrc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set frame data.
|
||||
*
|
||||
* @param data A native ByteBuffer pointing to the frame data.
|
||||
* @param width The width of the frame, in pixels
|
||||
* @param height The height of the frame, in pixels
|
||||
* @param stride The number of bytes in each row of image data
|
||||
* @param pixelFormat The PixelFormat of the frame
|
||||
*/
|
||||
public void setData(ByteBuffer data, int width, int height, int stride, PixelFormat pixelFormat) {
|
||||
if (!data.isDirect()) {
|
||||
throw new UnsupportedOperationException("ByteBuffer must be direct");
|
||||
}
|
||||
m_data = data;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_stride = stride;
|
||||
m_pixelFormat = pixelFormat;
|
||||
WPIUtilJNI.setRawFrameData(
|
||||
m_nativeObj, data, data.limit(), width, height, stride, pixelFormat.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to set frame information.
|
||||
*
|
||||
* @param width The width of the frame, in pixels
|
||||
* @param height The height of the frame, in pixels
|
||||
* @param stride The number of bytes in each row of image data
|
||||
* @param pixelFormat The PixelFormat of the frame
|
||||
*/
|
||||
public void setInfo(int width, int height, int stride, PixelFormat pixelFormat) {
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_stride = stride;
|
||||
m_pixelFormat = pixelFormat;
|
||||
WPIUtilJNI.setRawFrameInfo(
|
||||
m_nativeObj,
|
||||
m_data != null ? m_data.limit() : 0,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
pixelFormat.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this frame's timestamp info.
|
||||
*
|
||||
* @param frameTime the time this frame was grabbed at. This uses the same time base as
|
||||
* wpi::Now(), in us.
|
||||
* @param frameTimeSource the time source for the timestamp this frame was grabbed at.
|
||||
*/
|
||||
public void setTimeInfo(long frameTime, TimestampSource frameTimeSource) {
|
||||
m_time = frameTime;
|
||||
m_timeSource = frameTimeSource;
|
||||
WPIUtilJNI.setRawFrameTime(m_nativeObj, frameTime, frameTimeSource.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pointer to native representation of this frame.
|
||||
*
|
||||
* @return The pointer to native representation of this frame.
|
||||
*/
|
||||
public long getNativeObj() {
|
||||
return m_nativeObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ByteBuffer pointing to the frame data. This ByteBuffer is backed by the frame directly.
|
||||
* Its lifetime is controlled by the frame. If a new frame gets read, it will overwrite the
|
||||
* current one.
|
||||
*
|
||||
* @return A ByteBuffer pointing to the frame data.
|
||||
*/
|
||||
public ByteBuffer getData() {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a long (is a uint8_t* in native code) pointing to the frame data. This pointer is backed by
|
||||
* the frame directly. Its lifetime is controlled by the frame. If a new frame gets read, it will
|
||||
* overwrite the current one.
|
||||
*
|
||||
* @return A long pointing to the frame data.
|
||||
*/
|
||||
public long getDataPtr() {
|
||||
return WPIUtilJNI.getRawFrameDataPtr(m_nativeObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total size of the data stored in the frame, in bytes.
|
||||
*
|
||||
* @return The total size of the data stored in the frame.
|
||||
*/
|
||||
public int getSize() {
|
||||
return m_data != null ? m_data.limit() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of the image.
|
||||
*
|
||||
* @return The width of the image, in pixels.
|
||||
*/
|
||||
public int getWidth() {
|
||||
return m_width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the image.
|
||||
*
|
||||
* @return The height of the image, in pixels.
|
||||
*/
|
||||
public int getHeight() {
|
||||
return m_height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes in each row of image data.
|
||||
*
|
||||
* @return The image data stride, in bytes.
|
||||
*/
|
||||
public int getStride() {
|
||||
return m_stride;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PixelFormat of the frame.
|
||||
*
|
||||
* @return The PixelFormat of the frame.
|
||||
*/
|
||||
public PixelFormat getPixelFormat() {
|
||||
return m_pixelFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time this frame was grabbed at. This uses the same time base as wpi::Now(), in us.
|
||||
*
|
||||
* @return Time in 1 us increments.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return m_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time source for the timestamp this frame was grabbed at.
|
||||
*
|
||||
* @return Time source
|
||||
*/
|
||||
public TimestampSource getTimestampSource() {
|
||||
return m_timeSource;
|
||||
}
|
||||
}
|
||||
51
wpiutil/src/main/java/org/wpilib/util/TimestampSource.java
Normal file
51
wpiutil/src/main/java/org/wpilib/util/TimestampSource.java
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Options for where the timestamp an {@link RawFrame} was captured at can be measured relative to.
|
||||
*/
|
||||
public enum TimestampSource {
|
||||
/** unknown. */
|
||||
kUnknown(0),
|
||||
/**
|
||||
* wpi::Now when the new frame was dequeued by CSCore. Does not account for camera exposure time
|
||||
* or V4L latency.
|
||||
*/
|
||||
kFrameDequeue(1),
|
||||
/** End of Frame. Same as V4L2_BUF_FLAG_TSTAMP_SRC_EOF, translated into wpi::Now's timebase. */
|
||||
kV4LEOF(2),
|
||||
/**
|
||||
* Start of Exposure. Same as V4L2_BUF_FLAG_TSTAMP_SRC_SOE, translated into wpi::Now's timebase.
|
||||
*/
|
||||
kV4LSOE(3);
|
||||
|
||||
private final int value;
|
||||
|
||||
TimestampSource(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the integer value of the pixel format.
|
||||
*
|
||||
* @return Integer value
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static final TimestampSource[] s_values = values();
|
||||
|
||||
/**
|
||||
* Gets a TimestampSource enum value from its integer value.
|
||||
*
|
||||
* @param timestampSource integer value
|
||||
* @return Enum value
|
||||
*/
|
||||
public static TimestampSource getFromInt(int timestampSource) {
|
||||
return s_values[timestampSource];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// 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;
|
||||
|
||||
/** Marker interface to indicate a class is serializable using WPI serialization methods. */
|
||||
public interface WPISerializable {}
|
||||
227
wpiutil/src/main/java/org/wpilib/util/WPIUtilJNI.java
Normal file
227
wpiutil/src/main/java/org/wpilib/util/WPIUtilJNI.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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** WPIUtil JNI. */
|
||||
public class WPIUtilJNI {
|
||||
static boolean libraryLoaded = false;
|
||||
|
||||
/** Sets whether JNI should be loaded in the static block. */
|
||||
public static class Helper {
|
||||
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
|
||||
|
||||
/**
|
||||
* Returns true if the JNI should be loaded in the static block.
|
||||
*
|
||||
* @return True if the JNI should be loaded in the static block.
|
||||
*/
|
||||
public static boolean getExtractOnStaticLoad() {
|
||||
return extractOnStaticLoad.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the JNI should be loaded in the static block.
|
||||
*
|
||||
* @param load Whether the JNI should be loaded in the static block.
|
||||
*/
|
||||
public static void setExtractOnStaticLoad(boolean load) {
|
||||
extractOnStaticLoad.set(load);
|
||||
}
|
||||
|
||||
/** Utility class. */
|
||||
private Helper() {}
|
||||
}
|
||||
|
||||
static {
|
||||
if (Helper.getExtractOnStaticLoad()) {
|
||||
try {
|
||||
RuntimeLoader.loadLibrary("wpiutiljni");
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
libraryLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force load the library.
|
||||
*
|
||||
* @throws IOException if the library failed to load
|
||||
*/
|
||||
public static synchronized void forceLoad() throws IOException {
|
||||
if (libraryLoaded) {
|
||||
return;
|
||||
}
|
||||
RuntimeLoader.loadLibrary("wpiutiljni");
|
||||
libraryLoaded = true;
|
||||
}
|
||||
|
||||
/** Checks if the MSVC runtime is valid. Throws a runtime exception if not. */
|
||||
public static native void checkMsvcRuntime();
|
||||
|
||||
/**
|
||||
* Write the given string to stderr.
|
||||
*
|
||||
* @param str String to write.
|
||||
*/
|
||||
public static native void writeStderr(String str);
|
||||
|
||||
/** Enable mock time. */
|
||||
public static native void enableMockTime();
|
||||
|
||||
/** Disable mock time. */
|
||||
public static native void disableMockTime();
|
||||
|
||||
/**
|
||||
* Set mock time.
|
||||
*
|
||||
* @param time The desired time in microseconds.
|
||||
*/
|
||||
public static native void setMockTime(long time);
|
||||
|
||||
/**
|
||||
* Returns the time.
|
||||
*
|
||||
* @return The time.
|
||||
*/
|
||||
public static native long now();
|
||||
|
||||
/**
|
||||
* Returns the system time.
|
||||
*
|
||||
* @return The system time.
|
||||
*/
|
||||
public static native long getSystemTime();
|
||||
|
||||
/**
|
||||
* Creates an event. Events have binary state (signaled or not signaled) and may be either
|
||||
* automatically reset or manually reset. Automatic-reset events go to non-signaled state when a
|
||||
* WaitForObject is woken up by the event; manual-reset events require ResetEvent() to be called
|
||||
* to set the event to non-signaled state; if ResetEvent() is not called, any waiter on that event
|
||||
* will immediately wake when called.
|
||||
*
|
||||
* @param manualReset true for manual reset, false for automatic reset
|
||||
* @param initialState true to make the event initially in signaled state
|
||||
* @return Event handle
|
||||
*/
|
||||
public static native int createEvent(boolean manualReset, boolean initialState);
|
||||
|
||||
/**
|
||||
* Destroys an event. Destruction wakes up any waiters.
|
||||
*
|
||||
* @param eventHandle event handle
|
||||
*/
|
||||
public static native void destroyEvent(int eventHandle);
|
||||
|
||||
/**
|
||||
* Sets an event to signaled state.
|
||||
*
|
||||
* @param eventHandle event handle
|
||||
*/
|
||||
public static native void setEvent(int eventHandle);
|
||||
|
||||
/**
|
||||
* Sets an event to non-signaled state.
|
||||
*
|
||||
* @param eventHandle event handle
|
||||
*/
|
||||
public static native void resetEvent(int eventHandle);
|
||||
|
||||
/**
|
||||
* Creates a semaphore. Semaphores keep an internal counter. Releasing the semaphore increases the
|
||||
* count. A semaphore with a non-zero count is considered signaled. When a waiter wakes up it
|
||||
* atomically decrements the count by 1. This is generally useful in a single-supplier,
|
||||
* multiple-consumer scenario.
|
||||
*
|
||||
* @param initialCount initial value for the semaphore's internal counter
|
||||
* @param maximumCount maximum value for the semaphore's internal counter
|
||||
* @return Semaphore handle
|
||||
*/
|
||||
public static native int createSemaphore(int initialCount, int maximumCount);
|
||||
|
||||
/**
|
||||
* Destroys a semaphore. Destruction wakes up any waiters.
|
||||
*
|
||||
* @param semHandle semaphore handle
|
||||
*/
|
||||
public static native void destroySemaphore(int semHandle);
|
||||
|
||||
/**
|
||||
* Releases N counts of a semaphore.
|
||||
*
|
||||
* @param semHandle semaphore handle
|
||||
* @param releaseCount amount to add to semaphore's internal counter; must be positive
|
||||
* @return True on successful release, false on failure (e.g. release count would exceed maximum
|
||||
* value, or handle invalid)
|
||||
*/
|
||||
public static native boolean releaseSemaphore(int semHandle, int releaseCount);
|
||||
|
||||
static native long allocateRawFrame();
|
||||
|
||||
static native void freeRawFrame(long frame);
|
||||
|
||||
static native long getRawFrameDataPtr(long frame);
|
||||
|
||||
static native void setRawFrameData(
|
||||
long frame, ByteBuffer data, int size, int width, int height, int stride, int pixelFormat);
|
||||
|
||||
static native void setRawFrameInfo(
|
||||
long frame, int size, int width, int height, int stride, int pixelFormat);
|
||||
|
||||
static native void setRawFrameTime(long frame, long timestamp, int timeSource);
|
||||
|
||||
/**
|
||||
* Waits for a handle to be signaled.
|
||||
*
|
||||
* @param handle handle to wait on
|
||||
* @throws InterruptedException on failure (e.g. object was destroyed)
|
||||
*/
|
||||
public static native void waitForObject(int handle) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Waits for a handle to be signaled, with timeout.
|
||||
*
|
||||
* @param handle handle to wait on
|
||||
* @param timeout timeout in seconds
|
||||
* @return True if timeout reached without handle being signaled
|
||||
* @throws InterruptedException on failure (e.g. object was destroyed)
|
||||
*/
|
||||
public static native boolean waitForObjectTimeout(int handle, double timeout)
|
||||
throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Waits for one or more handles to be signaled.
|
||||
*
|
||||
* <p>Invalid handles are treated as signaled; the returned array will have the handle error bit
|
||||
* set for any invalid handles.
|
||||
*
|
||||
* @param handles array of handles to wait on
|
||||
* @return array of signaled handles
|
||||
* @throws InterruptedException on failure (e.g. no objects were signaled)
|
||||
*/
|
||||
public static native int[] waitForObjects(int[] handles) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Waits for one or more handles to be signaled, with timeout.
|
||||
*
|
||||
* <p>Invalid handles are treated as signaled; the returned array will have the handle error bit
|
||||
* set for any invalid handles.
|
||||
*
|
||||
* @param handles array of handles to wait on
|
||||
* @param timeout timeout in seconds
|
||||
* @return array of signaled handles; empty if timeout reached without any handle being signaled
|
||||
* @throws InterruptedException on failure (e.g. no objects were signaled and no timeout)
|
||||
*/
|
||||
public static native int[] waitForObjectsTimeout(int[] handles, double timeout)
|
||||
throws InterruptedException;
|
||||
|
||||
/** Utility class. */
|
||||
protected WPIUtilJNI() {}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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.cleanup;
|
||||
|
||||
import edu.wpi.first.util.ErrorMessages;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* An object containing a Stack of AutoCloseable objects that are closed when this object is closed.
|
||||
*/
|
||||
public class CleanupPool implements AutoCloseable {
|
||||
// Use a Deque instead of a Stack, as Stack's iterators go the wrong way, and docs
|
||||
// state ArrayDeque is faster anyway.
|
||||
private final Deque<AutoCloseable> m_closers = new ArrayDeque<>();
|
||||
|
||||
/** Default constructor. */
|
||||
public CleanupPool() {}
|
||||
|
||||
/**
|
||||
* Registers an object in the object stack for cleanup.
|
||||
*
|
||||
* @param <T> The object type
|
||||
* @param object The object to register
|
||||
* @return The registered object
|
||||
*/
|
||||
public <T extends AutoCloseable> T register(T object) {
|
||||
ErrorMessages.requireNonNullParam(object, "object", "register");
|
||||
m_closers.addFirst(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an object from the cleanup stack.
|
||||
*
|
||||
* @param object the object to remove
|
||||
*/
|
||||
public void remove(AutoCloseable object) {
|
||||
m_closers.remove(object);
|
||||
}
|
||||
|
||||
/** Closes all objects in the stack. */
|
||||
@Override
|
||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||
public void close() {
|
||||
for (AutoCloseable autoCloseable : m_closers) {
|
||||
try {
|
||||
autoCloseable.close();
|
||||
} catch (Exception e) {
|
||||
// Swallow any exceptions on close
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
m_closers.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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.cleanup;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Implement this interface to have access to a `reflectionCleanup` method that can be called from
|
||||
* your `close` method, that will use reflection to find all `AutoCloseable` instance members and
|
||||
* close them.
|
||||
*/
|
||||
@SuppressWarnings("PMD.ImplicitFunctionalInterface")
|
||||
public interface ReflectionCleanup extends AutoCloseable {
|
||||
/**
|
||||
* Default implementation that uses reflection to find all AutoCloseable fields not marked
|
||||
* SkipCleanup and call close() on them. Call this from your `close()` method with the class level
|
||||
* you want to close.
|
||||
*
|
||||
* @param cls the class level to clean up
|
||||
*/
|
||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||
default void reflectionCleanup(Class<? extends ReflectionCleanup> cls) {
|
||||
if (!cls.isAssignableFrom(getClass())) {
|
||||
System.out.println("Passed in class is not assignable from \"this\"");
|
||||
System.out.println("Expected something in the hierarchy of" + cls.getName());
|
||||
System.out.println("This is " + getClass().getName());
|
||||
return;
|
||||
}
|
||||
for (Field field : cls.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(SkipCleanup.class)) {
|
||||
continue;
|
||||
}
|
||||
if (!AutoCloseable.class.isAssignableFrom(field.getType())) {
|
||||
continue;
|
||||
}
|
||||
if (field.trySetAccessible()) {
|
||||
try {
|
||||
AutoCloseable c = (AutoCloseable) field.get(this);
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore any exceptions
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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.cleanup;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** Attribute for telling JVM to skip object cleanup. */
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SkipCleanup {}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
import java.lang.ref.Cleaner.Cleanable;
|
||||
|
||||
/** Cleaner object for WPILib objects. */
|
||||
public final class WPICleaner {
|
||||
private WPICleaner() {
|
||||
throw new UnsupportedOperationException("This is a utility class!");
|
||||
}
|
||||
|
||||
private static final Cleaner instance = Cleaner.create();
|
||||
|
||||
/**
|
||||
* Register an object with the cleaner.
|
||||
*
|
||||
* @param object The object to register.
|
||||
* @param runnable The runnable to call on cleanup.
|
||||
* @return The registered Cleanable.
|
||||
*/
|
||||
public static Cleanable register(Object object, Runnable runnable) {
|
||||
return instance.register(object, runnable);
|
||||
}
|
||||
}
|
||||
70
wpiutil/src/main/java/org/wpilib/util/concurrent/Event.java
Normal file
70
wpiutil/src/main/java/org/wpilib/util/concurrent/Event.java
Normal file
@@ -0,0 +1,70 @@
|
||||
// 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.concurrent;
|
||||
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
|
||||
/**
|
||||
* An atomic signaling event for synchronization.
|
||||
*
|
||||
* <p>Events have binary state (signaled or not signaled) and may be either automatically reset or
|
||||
* manually reset. Automatic-reset events go to non-signaled state when a waitForObject is woken up
|
||||
* by the event; manual-reset events require reset() to be called to set the event to non-signaled
|
||||
* state; if reset() is not called, any waiter on that event will immediately wake when called.
|
||||
*/
|
||||
public final class Event implements AutoCloseable {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param manualReset true for manual reset, false for automatic reset
|
||||
* @param initialState true to make the event initially in signaled state
|
||||
*/
|
||||
public Event(boolean manualReset, boolean initialState) {
|
||||
m_handle = WPIUtilJNI.createEvent(manualReset, initialState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Initial state is false.
|
||||
*
|
||||
* @param manualReset true for manual reset, false for automatic reset
|
||||
*/
|
||||
public Event(boolean manualReset) {
|
||||
this(manualReset, false);
|
||||
}
|
||||
|
||||
/** Constructor. Automatic reset, initial state is false. */
|
||||
public Event() {
|
||||
this(false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (m_handle != 0) {
|
||||
WPIUtilJNI.destroyEvent(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the event handle (e.g. for waitForObject).
|
||||
*
|
||||
* @return handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
/** Sets the event to signaled state. */
|
||||
public void set() {
|
||||
WPIUtilJNI.setEvent(m_handle);
|
||||
}
|
||||
|
||||
/** Sets the event to non-signaled state. */
|
||||
public void reset() {
|
||||
WPIUtilJNI.resetEvent(m_handle);
|
||||
}
|
||||
|
||||
private int m_handle;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/** A thread-safe container for handling events. */
|
||||
public class EventVector {
|
||||
private final ReentrantLock m_lock = new ReentrantLock();
|
||||
private final List<Integer> m_events = new ArrayList<>();
|
||||
|
||||
/** Default constructor. */
|
||||
public EventVector() {}
|
||||
|
||||
/**
|
||||
* Adds an event to the event vector.
|
||||
*
|
||||
* @param handle The event to add
|
||||
*/
|
||||
public void add(int handle) {
|
||||
m_lock.lock();
|
||||
try {
|
||||
m_events.add(handle);
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event from the event vector.
|
||||
*
|
||||
* @param handle The event to remove
|
||||
*/
|
||||
public void remove(int handle) {
|
||||
m_lock.lock();
|
||||
try {
|
||||
m_events.removeIf(x -> x == handle);
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** Wakes up all events in the event vector. */
|
||||
public void wakeup() {
|
||||
m_lock.lock();
|
||||
try {
|
||||
for (int eventHandle : m_events) {
|
||||
WPIUtilJNI.setEvent(eventHandle);
|
||||
}
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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.concurrent;
|
||||
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
|
||||
/**
|
||||
* A semaphore for synchronization.
|
||||
*
|
||||
* <p>Semaphores keep an internal counter. Releasing the semaphore increases the count. A semaphore
|
||||
* with a non-zero count is considered signaled. When a waiter wakes up it atomically decrements the
|
||||
* count by 1. This is generally useful in a single-supplier, multiple-consumer scenario.
|
||||
*/
|
||||
public final class Semaphore implements AutoCloseable {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param initialCount initial value for the semaphore's internal counter
|
||||
* @param maximumCount maximum value for the semaphore's internal counter
|
||||
*/
|
||||
public Semaphore(int initialCount, int maximumCount) {
|
||||
m_handle = WPIUtilJNI.createSemaphore(initialCount, maximumCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Maximum count is Integer.MAX_VALUE.
|
||||
*
|
||||
* @param initialCount initial value for the semaphore's internal counter
|
||||
*/
|
||||
public Semaphore(int initialCount) {
|
||||
this(initialCount, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/** Constructor. Initial count is 0, maximum count is Integer.MAX_VALUE. */
|
||||
public Semaphore() {
|
||||
this(0, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (m_handle != 0) {
|
||||
WPIUtilJNI.destroySemaphore(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the semaphore handle (e.g. for waitForObject).
|
||||
*
|
||||
* @return handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases N counts of the semaphore.
|
||||
*
|
||||
* @param releaseCount amount to add to semaphore's internal counter; must be positive
|
||||
* @return True on successful release, false on failure (e.g. release count would exceed maximum
|
||||
* value, or handle invalid)
|
||||
*/
|
||||
public boolean release(int releaseCount) {
|
||||
return WPIUtilJNI.releaseSemaphore(m_handle, releaseCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases 1 count of the semaphore.
|
||||
*
|
||||
* @return True on successful release, false on failure (e.g. release count would exceed maximum
|
||||
* value, or handle invalid)
|
||||
*/
|
||||
public boolean release() {
|
||||
return release(1);
|
||||
}
|
||||
|
||||
private int m_handle;
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* This is a simple circular buffer so we don't need to "bucket brigade" copy old values.
|
||||
*
|
||||
* @param <T> Buffer element type.
|
||||
*/
|
||||
public class CircularBuffer<T> {
|
||||
private T[] m_data;
|
||||
|
||||
// Index of element at front of buffer
|
||||
private int m_front;
|
||||
|
||||
// Number of elements used in buffer
|
||||
private int m_length;
|
||||
|
||||
/**
|
||||
* Create a CircularBuffer with the provided size.
|
||||
*
|
||||
* @param size Maximum number of buffer elements.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public CircularBuffer(int size) {
|
||||
m_data = (T[]) new Object[size];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of elements in buffer.
|
||||
*
|
||||
* @return number of elements in buffer
|
||||
*/
|
||||
public int size() {
|
||||
return m_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value at front of buffer.
|
||||
*
|
||||
* @return value at front of buffer
|
||||
*/
|
||||
public T getFirst() {
|
||||
return m_data[m_front];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value at back of buffer.
|
||||
*
|
||||
* @return value at back of buffer
|
||||
* @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index >=
|
||||
* size())
|
||||
*/
|
||||
public T getLast() {
|
||||
// If there are no elements in the buffer, do nothing
|
||||
if (m_length == 0) {
|
||||
throw new IndexOutOfBoundsException("getLast() called on an empty container");
|
||||
}
|
||||
|
||||
return m_data[(m_front + m_length - 1) % m_data.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Push new value onto front of the buffer. The value at the back is overwritten if the buffer is
|
||||
* full.
|
||||
*
|
||||
* @param value The value to push.
|
||||
*/
|
||||
public void addFirst(T value) {
|
||||
if (m_data.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_front = moduloDec(m_front);
|
||||
|
||||
m_data[m_front] = value;
|
||||
|
||||
if (m_length < m_data.length) {
|
||||
m_length++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push new value onto back of the buffer. The value at the front is overwritten if the buffer is
|
||||
* full.
|
||||
*
|
||||
* @param value The value to push.
|
||||
*/
|
||||
public void addLast(T value) {
|
||||
if (m_data.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_data[(m_front + m_length) % m_data.length] = value;
|
||||
|
||||
if (m_length < m_data.length) {
|
||||
m_length++;
|
||||
} else {
|
||||
// Increment front if buffer is full to maintain size
|
||||
m_front = moduloInc(m_front);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop value at front of buffer.
|
||||
*
|
||||
* @return value at front of buffer
|
||||
* @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index >=
|
||||
* size())
|
||||
*/
|
||||
public T removeFirst() {
|
||||
// If there are no elements in the buffer, do nothing
|
||||
if (m_length == 0) {
|
||||
throw new IndexOutOfBoundsException("removeFirst() called on an empty container");
|
||||
}
|
||||
|
||||
T temp = m_data[m_front];
|
||||
m_front = moduloInc(m_front);
|
||||
m_length--;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop value at back of buffer.
|
||||
*
|
||||
* @return value at back of buffer
|
||||
* @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index >=
|
||||
* size())
|
||||
*/
|
||||
public T removeLast() {
|
||||
// If there are no elements in the buffer, do nothing
|
||||
if (m_length == 0) {
|
||||
throw new IndexOutOfBoundsException("removeLast() called on an empty container");
|
||||
}
|
||||
|
||||
m_length--;
|
||||
return m_data[(m_front + m_length) % m_data.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes internal buffer to given size.
|
||||
*
|
||||
* <p>A new buffer is allocated because arrays are not resizable.
|
||||
*
|
||||
* @param size New buffer size.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void resize(int size) {
|
||||
var newBuffer = (T[]) new Object[size];
|
||||
m_length = Math.min(m_length, size);
|
||||
for (int i = 0; i < m_length; i++) {
|
||||
newBuffer[i] = m_data[(m_front + i) % m_data.length];
|
||||
}
|
||||
m_data = newBuffer;
|
||||
m_front = 0;
|
||||
}
|
||||
|
||||
/** Sets internal buffer contents to zero. */
|
||||
public void clear() {
|
||||
m_front = 0;
|
||||
m_length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element at the provided index relative to the start of the buffer.
|
||||
*
|
||||
* @param index Index into the buffer.
|
||||
* @return Element at index starting from front of buffer.
|
||||
*/
|
||||
public T get(int index) {
|
||||
return m_data[(m_front + index) % m_data.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment an index modulo the length of the buffer.
|
||||
*
|
||||
* @param index Index into the buffer.
|
||||
* @return The incremented index.
|
||||
*/
|
||||
private int moduloInc(int index) {
|
||||
return (index + 1) % m_data.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement an index modulo the length of the buffer.
|
||||
*
|
||||
* @param index Index into the buffer.
|
||||
* @return The decremented index.
|
||||
*/
|
||||
private int moduloDec(int index) {
|
||||
if (index == 0) {
|
||||
return m_data.length - 1;
|
||||
} else {
|
||||
return index - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/** This is a simple circular buffer so we don't need to "bucket brigade" copy old values. */
|
||||
public class DoubleCircularBuffer {
|
||||
private double[] m_data;
|
||||
|
||||
// Index of element at front of buffer
|
||||
private int m_front;
|
||||
|
||||
// Number of elements used in buffer
|
||||
private int m_length;
|
||||
|
||||
/**
|
||||
* Create a CircularBuffer with the provided size.
|
||||
*
|
||||
* @param size The size of the circular buffer.
|
||||
*/
|
||||
public DoubleCircularBuffer(int size) {
|
||||
m_data = new double[size];
|
||||
Arrays.fill(m_data, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of elements in buffer.
|
||||
*
|
||||
* @return number of elements in buffer
|
||||
*/
|
||||
public int size() {
|
||||
return m_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value at front of buffer.
|
||||
*
|
||||
* @return value at front of buffer
|
||||
*/
|
||||
public double getFirst() {
|
||||
return m_data[m_front];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value at back of buffer.
|
||||
*
|
||||
* @return value at back of buffer
|
||||
*/
|
||||
public double getLast() {
|
||||
// If there are no elements in the buffer, do nothing
|
||||
if (m_length == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return m_data[(m_front + m_length - 1) % m_data.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Push new value onto front of the buffer. The value at the back is overwritten if the buffer is
|
||||
* full.
|
||||
*
|
||||
* @param value The value to push.
|
||||
*/
|
||||
public void addFirst(double value) {
|
||||
if (m_data.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_front = moduloDec(m_front);
|
||||
|
||||
m_data[m_front] = value;
|
||||
|
||||
if (m_length < m_data.length) {
|
||||
m_length++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push new value onto back of the buffer. The value at the front is overwritten if the buffer is
|
||||
* full.
|
||||
*
|
||||
* @param value The value to push.
|
||||
*/
|
||||
public void addLast(double value) {
|
||||
if (m_data.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_data[(m_front + m_length) % m_data.length] = value;
|
||||
|
||||
if (m_length < m_data.length) {
|
||||
m_length++;
|
||||
} else {
|
||||
// Increment front if buffer is full to maintain size
|
||||
m_front = moduloInc(m_front);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop value at front of buffer.
|
||||
*
|
||||
* @return value at front of buffer
|
||||
*/
|
||||
public double removeFirst() {
|
||||
// If there are no elements in the buffer, do nothing
|
||||
if (m_length == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double temp = m_data[m_front];
|
||||
m_front = moduloInc(m_front);
|
||||
m_length--;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop value at back of buffer.
|
||||
*
|
||||
* @return value at back of buffer
|
||||
*/
|
||||
public double removeLast() {
|
||||
// If there are no elements in the buffer, do nothing
|
||||
if (m_length == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
m_length--;
|
||||
return m_data[(m_front + m_length) % m_data.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes internal buffer to given size.
|
||||
*
|
||||
* <p>A new buffer is allocated because arrays are not resizable.
|
||||
*
|
||||
* @param size New buffer size.
|
||||
*/
|
||||
public void resize(int size) {
|
||||
double[] newBuffer = new double[size];
|
||||
m_length = Math.min(m_length, size);
|
||||
for (int i = 0; i < m_length; i++) {
|
||||
newBuffer[i] = m_data[(m_front + i) % m_data.length];
|
||||
}
|
||||
m_data = newBuffer;
|
||||
m_front = 0;
|
||||
}
|
||||
|
||||
/** Sets internal buffer contents to zero. */
|
||||
public void clear() {
|
||||
Arrays.fill(m_data, 0.0);
|
||||
m_front = 0;
|
||||
m_length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element at the provided index relative to the start of the buffer.
|
||||
*
|
||||
* @param index Index into the buffer.
|
||||
* @return Element at index starting from front of buffer.
|
||||
*/
|
||||
public double get(int index) {
|
||||
return m_data[(m_front + index) % m_data.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment an index modulo the length of the buffer.
|
||||
*
|
||||
* @param index Index into the buffer.
|
||||
* @return The incremented index.
|
||||
*/
|
||||
private int moduloInc(int index) {
|
||||
return (index + 1) % m_data.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement an index modulo the length of the buffer.
|
||||
*
|
||||
* @param index Index into the buffer.
|
||||
* @return The decremented index.
|
||||
*/
|
||||
private int moduloDec(int index) {
|
||||
if (index == 0) {
|
||||
return m_data.length - 1;
|
||||
} else {
|
||||
return index - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.function;
|
||||
|
||||
/**
|
||||
* Represents an operation that accepts a single boolean-valued argument and returns no result. This
|
||||
* is the primitive type specialization of {@link java.util.function.Consumer} for boolean. Unlike
|
||||
* most other functional interfaces, BooleanConsumer is expected to operate via side effects.
|
||||
*
|
||||
* <p>This is a functional interface whose functional method is {@link #accept(boolean)}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BooleanConsumer {
|
||||
/**
|
||||
* Performs this operation on the given argument.
|
||||
*
|
||||
* @param value the input argument
|
||||
*/
|
||||
void accept(boolean value);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.function;
|
||||
|
||||
/**
|
||||
* Represents an operation that accepts a single float-valued argument and returns no result. This
|
||||
* is the primitive type specialization of {@link java.util.function.Consumer} for float. Unlike
|
||||
* most other functional interfaces, BooleanConsumer is expected to operate via side effects.
|
||||
*
|
||||
* <p>This is a functional interface whose functional method is {@link #accept(float)}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FloatConsumer {
|
||||
/**
|
||||
* Performs this operation on the given argument.
|
||||
*
|
||||
* @param value the input argument
|
||||
*/
|
||||
void accept(float value);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.function;
|
||||
|
||||
/**
|
||||
* Represents a supplier of float-valued results.
|
||||
*
|
||||
* <p>This is the float-producing primitive specialization of {@link java.util.function.Supplier}.
|
||||
*
|
||||
* <p>There is no requirement that a distinct result be returned each time the supplier is invoked.
|
||||
*
|
||||
* <p>This is a functional interface whose functional method is {@link #getAsFloat()}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FloatSupplier {
|
||||
/**
|
||||
* Gets a result.
|
||||
*
|
||||
* @return a result
|
||||
*/
|
||||
float getAsFloat();
|
||||
}
|
||||
212
wpiutil/src/main/java/org/wpilib/util/protobuf/Protobuf.java
Normal file
212
wpiutil/src/main/java/org/wpilib/util/protobuf/Protobuf.java
Normal file
@@ -0,0 +1,212 @@
|
||||
// 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.protobuf;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import us.hebi.quickbuf.Descriptors.Descriptor;
|
||||
import us.hebi.quickbuf.Descriptors.FileDescriptor;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
import us.hebi.quickbuf.RepeatedDouble;
|
||||
import us.hebi.quickbuf.RepeatedMessage;
|
||||
|
||||
/**
|
||||
* Interface for Protobuf serialization.
|
||||
*
|
||||
* <p>This is designed for serialization of more complex data structures including forward/backwards
|
||||
* compatibility and repeated/nested/variable length members, etc. Serialization and deserialization
|
||||
* code is auto-generated from .proto interface descriptions (the MessageType generic parameter).
|
||||
*
|
||||
* <p>Idiomatically, classes that support protobuf serialization should provide a static final
|
||||
* member named "proto" that provides an instance of an implementation of this interface, or a
|
||||
* static final method named "getProto" if the class is generic.
|
||||
*
|
||||
* @param <T> object type
|
||||
* @param <MessageType> protobuf message type
|
||||
*/
|
||||
public interface Protobuf<T, MessageType extends ProtoMessage<?>> {
|
||||
/**
|
||||
* Gets the Class object for the stored value.
|
||||
*
|
||||
* @return Class
|
||||
*/
|
||||
Class<T> getTypeClass();
|
||||
|
||||
/**
|
||||
* Gets the type string (e.g. for NetworkTables). This should be globally unique and start with
|
||||
* "proto:".
|
||||
*
|
||||
* @return type string
|
||||
*/
|
||||
default String getTypeString() {
|
||||
return "proto:" + getDescriptor().getFullName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the protobuf descriptor.
|
||||
*
|
||||
* @return descriptor
|
||||
*/
|
||||
Descriptor getDescriptor();
|
||||
|
||||
/**
|
||||
* Creates protobuf message.
|
||||
*
|
||||
* @return protobuf message
|
||||
*/
|
||||
MessageType createMessage();
|
||||
|
||||
/**
|
||||
* Deserializes an object from a protobuf message.
|
||||
*
|
||||
* @param msg protobuf message
|
||||
* @return New object
|
||||
*/
|
||||
T unpack(MessageType msg);
|
||||
|
||||
/**
|
||||
* Copies the object contents into a protobuf message. Implementations should call either
|
||||
* msg.setMember(member) or member.copyToProto(msg.getMutableMember()) for each member.
|
||||
*
|
||||
* @param msg protobuf message
|
||||
* @param value object to serialize
|
||||
*/
|
||||
void pack(MessageType msg, T value);
|
||||
|
||||
/**
|
||||
* Updates the object contents from a protobuf message. Implementations should call
|
||||
* msg.getMember(member), MemberClass.makeFromProto(msg.getMember()), or
|
||||
* member.updateFromProto(msg.getMember()) for each member.
|
||||
*
|
||||
* <p>Immutable classes cannot and should not implement this function. The default implementation
|
||||
* throws UnsupportedOperationException.
|
||||
*
|
||||
* @param out object to update
|
||||
* @param msg protobuf message
|
||||
* @throws UnsupportedOperationException if the object is immutable
|
||||
*/
|
||||
default void unpackInto(T out, MessageType msg) {
|
||||
throw new UnsupportedOperationException("object does not support unpackInto");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops over all protobuf descriptors including nested/referenced descriptors.
|
||||
*
|
||||
* @param exists function that returns false if fn should be called for the given type string
|
||||
* @param fn function to call for each descriptor
|
||||
*/
|
||||
default void forEachDescriptor(Predicate<String> exists, BiConsumer<String, byte[]> fn) {
|
||||
forEachDescriptorImpl(getDescriptor().getFile(), exists, fn);
|
||||
}
|
||||
|
||||
private static void forEachDescriptorImpl(
|
||||
FileDescriptor desc, Predicate<String> exists, BiConsumer<String, byte[]> fn) {
|
||||
String name = "proto:" + desc.getFullName();
|
||||
if (exists.test(name)) {
|
||||
return;
|
||||
}
|
||||
for (FileDescriptor dep : desc.getDependencies()) {
|
||||
forEachDescriptorImpl(dep, exists, fn);
|
||||
}
|
||||
fn.accept(name, desc.toProtoBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack a serialized protobuf array message.
|
||||
*
|
||||
* @param <T> object type
|
||||
* @param <MessageType> element type of the protobuf array
|
||||
* @param msg protobuf array message
|
||||
* @param proto protobuf implementation
|
||||
* @return Deserialized array
|
||||
*/
|
||||
static <T, MessageType extends ProtoMessage<MessageType>> T[] unpackArray(
|
||||
RepeatedMessage<MessageType> msg, Protobuf<T, MessageType> proto) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] result = (T[]) Array.newInstance(proto.getTypeClass(), msg.length());
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = proto.unpack(msg.get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack a serialized protobuf double array message.
|
||||
*
|
||||
* @param msg protobuf double array message
|
||||
* @return Deserialized array
|
||||
*/
|
||||
static double[] unpackArray(RepeatedDouble msg) {
|
||||
double[] result = new double[msg.length()];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = msg.get(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack a serialized protobuf array message.
|
||||
*
|
||||
* @param <T> object type
|
||||
* @param <MessageType> element type of the protobuf array
|
||||
* @param msg protobuf array message
|
||||
* @param arr array of objects
|
||||
* @param proto protobuf implementation
|
||||
*/
|
||||
static <T, MessageType extends ProtoMessage<MessageType>> void packArray(
|
||||
RepeatedMessage<MessageType> msg, T[] arr, Protobuf<T, MessageType> proto) {
|
||||
msg.clear();
|
||||
msg.reserve(arr.length);
|
||||
for (var obj : arr) {
|
||||
proto.pack(msg.next(), obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack a serialized protobuf double array message.
|
||||
*
|
||||
* @param msg protobuf double array message
|
||||
* @param arr array of objects
|
||||
*/
|
||||
static void packArray(RepeatedDouble msg, double[] arr) {
|
||||
msg.clear();
|
||||
msg.reserve(arr.length);
|
||||
msg.addAll(arr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// 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.protobuf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
import us.hebi.quickbuf.ProtoSink;
|
||||
import us.hebi.quickbuf.ProtoSource;
|
||||
|
||||
/**
|
||||
* Reusable buffer for serialization/deserialization to/from a protobuf.
|
||||
*
|
||||
* @param <T> object type
|
||||
* @param <MessageType> protobuf message type
|
||||
*/
|
||||
public final class ProtobufBuffer<T, MessageType extends ProtoMessage<?>> {
|
||||
private ProtobufBuffer(Protobuf<T, MessageType> proto) {
|
||||
m_buf = ByteBuffer.allocateDirect(1024);
|
||||
m_sink = ProtoSink.newDirectSink();
|
||||
m_sink.setOutput(m_buf);
|
||||
m_source = ProtoSource.newDirectSource();
|
||||
m_msg = proto.createMessage();
|
||||
m_proto = proto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ProtobufBuffer for the given Protobuf object.
|
||||
*
|
||||
* @param <T> The type to serialize.
|
||||
* @param <MessageType> The Protobuf message type.
|
||||
* @param proto The Protobuf object.
|
||||
* @return A ProtobufBuffer for the given Protobuf object.
|
||||
*/
|
||||
public static <T, MessageType extends ProtoMessage<?>> ProtobufBuffer<T, MessageType> create(
|
||||
Protobuf<T, MessageType> proto) {
|
||||
return new ProtobufBuffer<>(proto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the protobuf object of the stored type.
|
||||
*
|
||||
* @return protobuf object
|
||||
*/
|
||||
public Protobuf<T, MessageType> getProto() {
|
||||
return m_proto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type string.
|
||||
*
|
||||
* @return type string
|
||||
*/
|
||||
public String getTypeString() {
|
||||
return m_proto.getTypeString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws IOException if serialization failed
|
||||
*/
|
||||
public ByteBuffer write(T value) throws IOException {
|
||||
m_msg.clearQuick();
|
||||
m_proto.pack(m_msg, value);
|
||||
int size = m_msg.getSerializedSize();
|
||||
if (size > m_buf.capacity()) {
|
||||
m_buf = ByteBuffer.allocateDirect(size * 2);
|
||||
m_sink.setOutput(m_buf);
|
||||
}
|
||||
m_sink.reset();
|
||||
m_msg.writeTo(m_sink);
|
||||
m_buf.position(m_sink.getTotalBytesWritten());
|
||||
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
|
||||
* @throws IOException if deserialization failed
|
||||
*/
|
||||
public T read(byte[] buf, int start, int len) throws IOException {
|
||||
m_msg.clearQuick();
|
||||
m_source.setInput(buf, start, len);
|
||||
m_msg.mergeFrom(m_source);
|
||||
return m_proto.unpack(m_msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a value from a byte array, creating a new object.
|
||||
*
|
||||
* @param buf byte array
|
||||
* @return new object
|
||||
* @throws IOException if deserialization failed
|
||||
*/
|
||||
public T read(byte[] buf) throws IOException {
|
||||
return read(buf, 0, buf.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a value from a ByteBuffer, creating a new object.
|
||||
*
|
||||
* @param buf byte buffer
|
||||
* @return new object
|
||||
* @throws IOException if deserialization failed
|
||||
*/
|
||||
public T read(ByteBuffer buf) throws IOException {
|
||||
m_msg.clearQuick();
|
||||
m_source.setInput(buf);
|
||||
m_msg.mergeFrom(m_source);
|
||||
return m_proto.unpack(m_msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 IOException if deserialization failed
|
||||
* @throws UnsupportedOperationException if the object is immutable
|
||||
*/
|
||||
public void readInto(T out, byte[] buf, int start, int len) throws IOException {
|
||||
m_msg.clearQuick();
|
||||
m_source.setInput(buf, start, len);
|
||||
m_msg.mergeFrom(m_source);
|
||||
m_proto.unpackInto(out, m_msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 IOException if deserialization failed
|
||||
* @throws UnsupportedOperationException if the object is immutable
|
||||
*/
|
||||
public void readInto(T out, byte[] buf) throws IOException {
|
||||
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 IOException if deserialization failed
|
||||
* @throws UnsupportedOperationException if the object is immutable
|
||||
*/
|
||||
public void readInto(T out, ByteBuffer buf) throws IOException {
|
||||
m_msg.clearQuick();
|
||||
m_source.setInput(buf);
|
||||
m_msg.mergeFrom(m_source);
|
||||
m_proto.unpackInto(out, m_msg);
|
||||
}
|
||||
|
||||
private ByteBuffer m_buf;
|
||||
private final ProtoSink m_sink;
|
||||
private final ProtoSource m_source;
|
||||
private final MessageType m_msg;
|
||||
private final Protobuf<T, MessageType> m_proto;
|
||||
}
|
||||
@@ -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.protobuf;
|
||||
|
||||
import edu.wpi.first.util.WPISerializable;
|
||||
|
||||
/**
|
||||
* Marker interface to indicate a class is serializable using Protobuf serialization.
|
||||
*
|
||||
* <p>While this cannot be enforced by the interface, any class implementing this interface should
|
||||
* provide a public final static `proto` member variable, or a static final `getProto()` method if
|
||||
* the class is generic.
|
||||
*/
|
||||
public interface ProtobufSerializable extends WPISerializable {}
|
||||
@@ -0,0 +1,119 @@
|
||||
// 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;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Loads classes by name. Can be used at any time, but is most commonly used to preload classes at
|
||||
* the start of the program to avoid unpredictable delays due to lazy classloading later in program
|
||||
* execution.
|
||||
*/
|
||||
public final class ClassPreloader {
|
||||
private ClassPreloader() {}
|
||||
|
||||
/**
|
||||
* Loads classes from an iterable.
|
||||
*
|
||||
* @param classNames iterable of class names
|
||||
* @return Number of classes loaded.
|
||||
*/
|
||||
public static int preload(Iterable<String> classNames) {
|
||||
int count = 0;
|
||||
for (String i : classNames) {
|
||||
try {
|
||||
Class.forName(i);
|
||||
count++;
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.out.println("Could not preload " + i);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads classes.
|
||||
*
|
||||
* @param classNames array of class names
|
||||
* @return Number of classes loaded.
|
||||
*/
|
||||
public static int preload(String... classNames) {
|
||||
int count = 0;
|
||||
for (String i : classNames) {
|
||||
try {
|
||||
Class.forName(i);
|
||||
count++;
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.out.println("Could not preload " + i);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads classes from a buffered reader. The input is expected to be one class name per line.
|
||||
* Blank lines and lines starting with a semicolon are ignored.
|
||||
*
|
||||
* @param reader Reader
|
||||
* @return Number of classes loaded.
|
||||
*/
|
||||
public static int preload(BufferedReader reader) {
|
||||
int count = 0;
|
||||
try {
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
if (!line.isEmpty() && !line.startsWith(";")) {
|
||||
try {
|
||||
Class.forName(line);
|
||||
count++;
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.out.println("Could not preload " + line);
|
||||
}
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println("Error when reading preload file: " + e);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads classes from an input stream. The input is expected to be one class name per line. Blank
|
||||
* lines and lines starting with a semicolon are ignored.
|
||||
*
|
||||
* @param stream input stream
|
||||
* @return Number of classes loaded.
|
||||
*/
|
||||
public static int preload(InputStream stream) {
|
||||
return preload(new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads classes from a file. The input is expected to be one class name per line. Blank lines and
|
||||
* lines starting with a semicolon are ignored.
|
||||
*
|
||||
* @param filename filename
|
||||
* @return Number of classes loaded.
|
||||
*/
|
||||
public static int preloadFile(String filename) {
|
||||
try (BufferedReader reader =
|
||||
Files.newBufferedReader(Paths.get(filename), StandardCharsets.UTF_8)) {
|
||||
return preload(reader);
|
||||
} catch (NoSuchFileException e) {
|
||||
System.out.println("Could not open preload file " + filename + ": " + e);
|
||||
} catch (IOException e) {
|
||||
System.out.println("Could not close preload file " + filename + ": " + e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// 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;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Loads dynamic libraries for all platforms. */
|
||||
public final class CombinedRuntimeLoader {
|
||||
private CombinedRuntimeLoader() {}
|
||||
|
||||
private static String extractionDirectory;
|
||||
|
||||
/**
|
||||
* Returns library extraction directory.
|
||||
*
|
||||
* @return Library extraction directory.
|
||||
*/
|
||||
public static synchronized String getExtractionDirectory() {
|
||||
return extractionDirectory;
|
||||
}
|
||||
|
||||
private static synchronized void setExtractionDirectory(String directory) {
|
||||
extractionDirectory = directory;
|
||||
}
|
||||
|
||||
private static String defaultExtractionRoot;
|
||||
|
||||
/**
|
||||
* Gets the default extraction root location (~/.wpilib/nativecache) for use if
|
||||
* setExtractionDirectory is not set.
|
||||
*
|
||||
* @return The default extraction root location.
|
||||
*/
|
||||
public static synchronized String getDefaultExtractionRoot() {
|
||||
if (defaultExtractionRoot != null) {
|
||||
return defaultExtractionRoot;
|
||||
}
|
||||
String home = System.getProperty("user.home");
|
||||
defaultExtractionRoot = Paths.get(home, ".wpilib", "nativecache").toString();
|
||||
return defaultExtractionRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform path.
|
||||
*
|
||||
* @return The current platform path.
|
||||
* @throws IllegalStateException Thrown if the operating system is unknown.
|
||||
*/
|
||||
public static String getPlatformPath() {
|
||||
String filePath;
|
||||
String arch = System.getProperty("os.arch");
|
||||
|
||||
boolean intel32 = "x86".equals(arch) || "i386".equals(arch);
|
||||
boolean intel64 = "amd64".equals(arch) || "x86_64".equals(arch);
|
||||
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
if (intel32) {
|
||||
filePath = "/windows/x86/";
|
||||
} else {
|
||||
filePath = "/windows/x86-64/";
|
||||
}
|
||||
} else if (System.getProperty("os.name").startsWith("Mac")) {
|
||||
filePath = "/osx/universal/";
|
||||
} else if (System.getProperty("os.name").startsWith("Linux")) {
|
||||
if (intel32) {
|
||||
filePath = "/linux/x86/";
|
||||
} else if (intel64) {
|
||||
filePath = "/linux/x86-64/";
|
||||
} else if (new File("/usr/local/frc/bin/frcRunRobot.sh").exists()) {
|
||||
filePath = "/linux/athena/";
|
||||
} else if ("arm".equals(arch) || "arm32".equals(arch)) {
|
||||
filePath = "/linux/arm32/";
|
||||
} else if ("aarch64".equals(arch) || "arm64".equals(arch)) {
|
||||
filePath = "/linux/arm64/";
|
||||
} else {
|
||||
filePath = "/linux/nativearm/";
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private static String getLoadErrorMessage(String libraryName, UnsatisfiedLinkError ule) {
|
||||
StringBuilder msg = new StringBuilder(512);
|
||||
msg.append(libraryName)
|
||||
.append(" could not be loaded from path\n" + "\tattempted to load for platform ")
|
||||
.append(getPlatformPath())
|
||||
.append("\nLast Load Error: \n")
|
||||
.append(ule.getMessage())
|
||||
.append('\n');
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
msg.append(
|
||||
"A common cause of this error is missing the C++ runtime.\n"
|
||||
+ "Download the latest at https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads\n");
|
||||
}
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a list of native libraries.
|
||||
*
|
||||
* @param <T> The class where the resources would be located
|
||||
* @param clazz The actual class object
|
||||
* @param resourceName The resource name on the classpath to use for file lookup
|
||||
* @return List of all libraries that were extracted
|
||||
* @throws IOException Thrown if resource not found or file could not be extracted
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> List<String> extractLibraries(Class<T> clazz, String resourceName)
|
||||
throws IOException {
|
||||
TypeReference<HashMap<String, Object>> typeRef = new TypeReference<>() {};
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Map<String, Object> map;
|
||||
try (var stream = clazz.getResourceAsStream(resourceName)) {
|
||||
map = mapper.readValue(stream, typeRef);
|
||||
}
|
||||
|
||||
var platformPath = Paths.get(getPlatformPath());
|
||||
var platform = platformPath.getName(0).toString();
|
||||
var arch = platformPath.getName(1).toString();
|
||||
|
||||
var platformMap = (Map<String, List<String>>) map.get(platform);
|
||||
|
||||
var fileList = platformMap.get(arch);
|
||||
|
||||
var extractionPathString = getExtractionDirectory();
|
||||
|
||||
if (extractionPathString == null) {
|
||||
String hash = (String) map.get("hash");
|
||||
|
||||
var defaultExtractionRoot = getDefaultExtractionRoot();
|
||||
var extractionPath = Paths.get(defaultExtractionRoot, platform, arch, hash);
|
||||
extractionPathString = extractionPath.toString();
|
||||
|
||||
setExtractionDirectory(extractionPathString);
|
||||
}
|
||||
|
||||
List<String> extractedFiles = new ArrayList<>();
|
||||
|
||||
byte[] buffer = new byte[0x10000]; // 64K copy buffer
|
||||
|
||||
for (var file : fileList) {
|
||||
try (var stream = clazz.getResourceAsStream(file)) {
|
||||
Objects.requireNonNull(stream);
|
||||
|
||||
var outputFile = Paths.get(extractionPathString, new File(file).getName());
|
||||
extractedFiles.add(outputFile.toString());
|
||||
if (outputFile.toFile().exists()) {
|
||||
continue;
|
||||
}
|
||||
var parent = outputFile.getParent();
|
||||
if (parent == null) {
|
||||
throw new IOException("Output file has no parent");
|
||||
}
|
||||
parent.toFile().mkdirs();
|
||||
|
||||
try (var os = Files.newOutputStream(outputFile)) {
|
||||
int readBytes;
|
||||
while ((readBytes = stream.read(buffer)) != -1) { // NOPMD
|
||||
os.write(buffer, 0, readBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extractedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single library from a list of extracted files.
|
||||
*
|
||||
* @param libraryName The library name to load
|
||||
* @param extractedFiles The extracted files to search
|
||||
* @throws IOException If library was not found
|
||||
*/
|
||||
public static void loadLibrary(String libraryName, List<String> extractedFiles)
|
||||
throws IOException {
|
||||
String currentPath = null;
|
||||
try {
|
||||
for (var extractedFile : extractedFiles) {
|
||||
if (extractedFile.contains(libraryName)) {
|
||||
// Load it
|
||||
currentPath = extractedFile;
|
||||
System.load(extractedFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IOException("Could not find library " + libraryName);
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
throw new IOException(getLoadErrorMessage(currentPath, ule));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of native libraries out of a single directory.
|
||||
*
|
||||
* @param <T> The class where the resources would be located
|
||||
* @param clazz The actual class object
|
||||
* @param librariesToLoad List of libraries to load
|
||||
* @throws IOException Throws an IOException if not found
|
||||
*/
|
||||
public static <T> void loadLibraries(Class<T> clazz, String... librariesToLoad)
|
||||
throws IOException {
|
||||
// Extract everything
|
||||
|
||||
var extractedFiles = extractLibraries(clazz, "/ResourceInformation.json");
|
||||
|
||||
for (var library : librariesToLoad) {
|
||||
loadLibrary(library, extractedFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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;
|
||||
|
||||
/** Exception thrown due to a bad MSVC Runtime. */
|
||||
public class MsvcRuntimeException extends RuntimeException {
|
||||
private static final long serialVersionUID = -9155939328084105142L;
|
||||
|
||||
private static String generateMessage(
|
||||
int foundMajor, int foundMinor, int expectedMajor, int expectedMinor, String runtimePath) {
|
||||
String jvmLocation = ProcessHandle.current().info().command().orElse("Unknown");
|
||||
|
||||
StringBuilder builder = new StringBuilder(100);
|
||||
builder
|
||||
.append("Invalid MSVC Runtime Detected.\n")
|
||||
.append(
|
||||
String.format(
|
||||
"Expected at least %d.%d, but found %d.%d\n",
|
||||
expectedMajor, expectedMinor, foundMajor, foundMinor))
|
||||
.append(String.format("JVM Location: %s\n", jvmLocation))
|
||||
.append(String.format("Runtime DLL Location: %s\n", runtimePath))
|
||||
.append("See https://wpilib.org/jvmruntime for more information\n");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a runtime exception.
|
||||
*
|
||||
* @param foundMajor found major
|
||||
* @param foundMinor found minor
|
||||
* @param expectedMajor expected major
|
||||
* @param expectedMinor expected minor
|
||||
* @param runtimePath path of runtime
|
||||
*/
|
||||
public MsvcRuntimeException(
|
||||
int foundMajor, int foundMinor, int expectedMajor, int expectedMinor, String runtimePath) {
|
||||
super(generateMessage(foundMajor, foundMinor, expectedMajor, expectedMinor, runtimePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a runtime exception.
|
||||
*
|
||||
* @param msg message
|
||||
*/
|
||||
public MsvcRuntimeException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// 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;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* A utility class for detecting and providing platform-specific such as OS and CPU architecture.
|
||||
*
|
||||
* @deprecated platform detection is brittle and may be removed in the future.
|
||||
*/
|
||||
@Deprecated(since = "2025", forRemoval = true)
|
||||
public final class RuntimeDetector {
|
||||
private static String filePrefix;
|
||||
private static String fileExtension;
|
||||
private static String filePath;
|
||||
|
||||
private static synchronized void computePlatform() {
|
||||
if (fileExtension != null && filePath != null && filePrefix != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean intel32 = is32BitIntel();
|
||||
boolean intel64 = is64BitIntel();
|
||||
boolean arm64 = isArm64();
|
||||
|
||||
if (isWindows()) {
|
||||
filePrefix = "";
|
||||
fileExtension = ".dll";
|
||||
if (intel32) {
|
||||
filePath = "/windows/x86/";
|
||||
} else {
|
||||
filePath = "/windows/x86-64/";
|
||||
}
|
||||
} else if (isMac()) {
|
||||
filePrefix = "lib";
|
||||
fileExtension = ".dylib";
|
||||
filePath = "/osx/universal/";
|
||||
} else if (isLinux()) {
|
||||
filePrefix = "lib";
|
||||
fileExtension = ".so";
|
||||
if (intel32) {
|
||||
filePath = "/linux/x86/";
|
||||
} else if (intel64) {
|
||||
filePath = "/linux/x86-64/";
|
||||
} else if (isAthena()) {
|
||||
filePath = "/linux/athena/";
|
||||
} else if (isArm32()) {
|
||||
filePath = "/linux/arm32/";
|
||||
} else if (arm64) {
|
||||
filePath = "/linux/arm64/";
|
||||
} else {
|
||||
filePath = "/linux/nativearm/";
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Failed to determine OS");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file prefix for the current system.
|
||||
*
|
||||
* @return The file prefix.
|
||||
*/
|
||||
public static synchronized String getFilePrefix() {
|
||||
computePlatform();
|
||||
|
||||
return filePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file extension for the current system.
|
||||
*
|
||||
* @return The file extension.
|
||||
*/
|
||||
public static synchronized String getFileExtension() {
|
||||
computePlatform();
|
||||
|
||||
return fileExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the platform path for the current system.
|
||||
*
|
||||
* @return The platform path.
|
||||
*/
|
||||
public static synchronized String getPlatformPath() {
|
||||
computePlatform();
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the requested resource.
|
||||
*
|
||||
* @param libName Library name.
|
||||
* @return The path to the requested resource.
|
||||
*/
|
||||
public static synchronized String getLibraryResource(String libName) {
|
||||
computePlatform();
|
||||
|
||||
return filePath + filePrefix + libName + fileExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the hash to the requested resource.
|
||||
*
|
||||
* @param libName Library name.
|
||||
* @return The path to the hash to the requested resource.
|
||||
*/
|
||||
public static synchronized String getHashLibraryResource(String libName) {
|
||||
computePlatform();
|
||||
|
||||
return filePath + libName + ".hash";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if hardware platform is Athena.
|
||||
*
|
||||
* @return True if hardware platform is Athena.
|
||||
*/
|
||||
public static boolean isAthena() {
|
||||
File runRobotFile = new File("/usr/local/frc/bin/frcRunRobot.sh");
|
||||
return runRobotFile.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OS is Arm32.
|
||||
*
|
||||
* @return True if OS is Arm32.
|
||||
*/
|
||||
public static boolean isArm32() {
|
||||
String arch = System.getProperty("os.arch");
|
||||
return "arm".equals(arch) || "arm32".equals(arch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if architecture is Arm64.
|
||||
*
|
||||
* @return if architecture is Arm64.
|
||||
*/
|
||||
public static boolean isArm64() {
|
||||
String arch = System.getProperty("os.arch");
|
||||
return "aarch64".equals(arch) || "arm64".equals(arch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OS is Linux.
|
||||
*
|
||||
* @return if OS is Linux.
|
||||
*/
|
||||
public static boolean isLinux() {
|
||||
return System.getProperty("os.name").startsWith("Linux");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OS is Windows.
|
||||
*
|
||||
* @return if OS is Windows.
|
||||
*/
|
||||
public static boolean isWindows() {
|
||||
return System.getProperty("os.name").startsWith("Windows");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OS is Mac.
|
||||
*
|
||||
* @return if OS is Mac.
|
||||
*/
|
||||
public static boolean isMac() {
|
||||
return System.getProperty("os.name").startsWith("Mac");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OS is 32bit Intel.
|
||||
*
|
||||
* @return if OS is 32bit Intel.
|
||||
*/
|
||||
public static boolean is32BitIntel() {
|
||||
String arch = System.getProperty("os.arch");
|
||||
return "x86".equals(arch) || "i386".equals(arch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OS is 64bit Intel.
|
||||
*
|
||||
* @return if OS is 64bit Intel.
|
||||
*/
|
||||
public static boolean is64BitIntel() {
|
||||
String arch = System.getProperty("os.arch");
|
||||
return "amd64".equals(arch) || "x86_64".equals(arch);
|
||||
}
|
||||
|
||||
private RuntimeDetector() {}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** Loads a native library at runtime. */
|
||||
public final class RuntimeLoader {
|
||||
/**
|
||||
* Returns a load error message given the information in the provided UnsatisfiedLinkError.
|
||||
*
|
||||
* @param libraryName the name of the library that failed to load.
|
||||
* @param ule UnsatisfiedLinkError object.
|
||||
* @return A load error message.
|
||||
*/
|
||||
private static String getLoadErrorMessage(String libraryName, UnsatisfiedLinkError ule) {
|
||||
String jvmLocation = ProcessHandle.current().info().command().orElse("Unknown");
|
||||
StringBuilder msg = new StringBuilder(512);
|
||||
msg.append(libraryName)
|
||||
.append(" could not be loaded from path.\n" + "\tattempted to load for platform ")
|
||||
.append(CombinedRuntimeLoader.getPlatformPath())
|
||||
.append("\nLast Load Error: \n")
|
||||
.append(ule.getMessage())
|
||||
.append('\n')
|
||||
.append(String.format("JVM Location: %s\n", jvmLocation));
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
msg.append(
|
||||
"A common cause of this error is using a JVM with an incorrect MSVC runtime.\n"
|
||||
+ "Ensure you are using the WPILib JVM (The current running JVM is listed above)\n"
|
||||
+ "See https://wpilib.org/jvmruntime for more information\n");
|
||||
}
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a native library.
|
||||
*
|
||||
* @param libraryName the name of the library to load.
|
||||
* @throws IOException if the library fails to load
|
||||
*/
|
||||
public static void loadLibrary(String libraryName) throws IOException {
|
||||
try {
|
||||
System.loadLibrary(libraryName);
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
throw new IOException(getLoadErrorMessage(libraryName, ule));
|
||||
}
|
||||
}
|
||||
|
||||
private RuntimeLoader() {
|
||||
throw new IllegalStateException("This class shouldn't be instantiated");
|
||||
}
|
||||
}
|
||||
16
wpiutil/src/main/java/org/wpilib/util/sendable/Sendable.java
Normal file
16
wpiutil/src/main/java/org/wpilib/util/sendable/Sendable.java
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util.sendable;
|
||||
|
||||
/** The base interface for objects that can be sent over the network. */
|
||||
@SuppressWarnings("PMD.ImplicitFunctionalInterface")
|
||||
public interface Sendable {
|
||||
/**
|
||||
* Initializes this {@link Sendable} object.
|
||||
*
|
||||
* @param builder sendable builder
|
||||
*/
|
||||
void initSendable(SendableBuilder builder);
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// 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.sendable;
|
||||
|
||||
import edu.wpi.first.util.function.BooleanConsumer;
|
||||
import edu.wpi.first.util.function.FloatConsumer;
|
||||
import edu.wpi.first.util.function.FloatSupplier;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.DoubleConsumer;
|
||||
import java.util.function.DoubleSupplier;
|
||||
import java.util.function.LongConsumer;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Helper class for building Sendable dashboard representations. */
|
||||
public interface SendableBuilder extends AutoCloseable {
|
||||
/** The backend kinds used for the sendable builder. */
|
||||
enum BackendKind {
|
||||
/** Unknown. */
|
||||
kUnknown,
|
||||
|
||||
/** NetworkTables. */
|
||||
kNetworkTables
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the string representation of the named data type that will be used by the smart dashboard
|
||||
* for this sendable.
|
||||
*
|
||||
* @param type data type
|
||||
*/
|
||||
void setSmartDashboardType(String type);
|
||||
|
||||
/**
|
||||
* Set a flag indicating if this Sendable should be treated as an actuator. By default, this flag
|
||||
* is false.
|
||||
*
|
||||
* @param value true if actuator, false if not
|
||||
*/
|
||||
void setActuator(boolean value);
|
||||
|
||||
/**
|
||||
* Add a boolean property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter);
|
||||
|
||||
/**
|
||||
* Add a constant boolean property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstBoolean(String key, boolean value);
|
||||
|
||||
/**
|
||||
* Add an integer property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter);
|
||||
|
||||
/**
|
||||
* Add a constant integer property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstInteger(String key, long value);
|
||||
|
||||
/**
|
||||
* Add a float property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter);
|
||||
|
||||
/**
|
||||
* Add a constant float property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstFloat(String key, float value);
|
||||
|
||||
/**
|
||||
* Add a double property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter);
|
||||
|
||||
/**
|
||||
* Add a constant double property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstDouble(String key, double value);
|
||||
|
||||
/**
|
||||
* Add a string property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter);
|
||||
|
||||
/**
|
||||
* Add a constant string property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstString(String key, String value);
|
||||
|
||||
/**
|
||||
* Add a boolean array property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addBooleanArrayProperty(String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter);
|
||||
|
||||
/**
|
||||
* Add a constant boolean array property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstBooleanArray(String key, boolean[] value);
|
||||
|
||||
/**
|
||||
* Add an integer array property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addIntegerArrayProperty(String key, Supplier<long[]> getter, Consumer<long[]> setter);
|
||||
|
||||
/**
|
||||
* Add a constant integer property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstIntegerArray(String key, long[] value);
|
||||
|
||||
/**
|
||||
* Add a float array property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addFloatArrayProperty(String key, Supplier<float[]> getter, Consumer<float[]> setter);
|
||||
|
||||
/**
|
||||
* Add a constant float array property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstFloatArray(String key, float[] value);
|
||||
|
||||
/**
|
||||
* Add a double array property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addDoubleArrayProperty(String key, Supplier<double[]> getter, Consumer<double[]> setter);
|
||||
|
||||
/**
|
||||
* Add a constant double array property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstDoubleArray(String key, double[] value);
|
||||
|
||||
/**
|
||||
* Add a string array property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addStringArrayProperty(String key, Supplier<String[]> getter, Consumer<String[]> setter);
|
||||
|
||||
/**
|
||||
* Add a constant string array property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstStringArray(String key, String[] value);
|
||||
|
||||
/**
|
||||
* Add a raw property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param typeString type string
|
||||
* @param getter getter function (returns current value)
|
||||
* @param setter setter function (sets new value)
|
||||
*/
|
||||
void addRawProperty(
|
||||
String key, String typeString, Supplier<byte[]> getter, Consumer<byte[]> setter);
|
||||
|
||||
/**
|
||||
* Add a constant raw property.
|
||||
*
|
||||
* @param key property name
|
||||
* @param typeString type string
|
||||
* @param value the value
|
||||
*/
|
||||
void publishConstRaw(String key, String typeString, byte[] value);
|
||||
|
||||
/**
|
||||
* Gets the kind of backend being used.
|
||||
*
|
||||
* @return Backend kind
|
||||
*/
|
||||
BackendKind getBackendKind();
|
||||
|
||||
/**
|
||||
* Return whether this sendable has been published.
|
||||
*
|
||||
* @return True if it has been published, false if not.
|
||||
*/
|
||||
boolean isPublished();
|
||||
|
||||
/** Update the published values by calling the getters for all properties. */
|
||||
void update();
|
||||
|
||||
/** Clear properties. */
|
||||
void clearProperties();
|
||||
|
||||
/**
|
||||
* Adds a closeable. The closeable.close() will be called when close() is called.
|
||||
*
|
||||
* @param closeable closeable object
|
||||
*/
|
||||
void addCloseable(AutoCloseable closeable);
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
// 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.sendable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* The SendableRegistry class is the public interface for registering sensors and actuators for use
|
||||
* on dashboards.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||
public final class SendableRegistry {
|
||||
private static class Component implements AutoCloseable {
|
||||
Component() {}
|
||||
|
||||
Component(Sendable sendable) {
|
||||
m_sendable = new WeakReference<>(sendable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
m_builder.close();
|
||||
for (AutoCloseable data : m_data) {
|
||||
if (data != null) {
|
||||
data.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WeakReference<Sendable> m_sendable;
|
||||
SendableBuilder m_builder;
|
||||
String m_name;
|
||||
String m_subsystem = "Ungrouped";
|
||||
AutoCloseable[] m_data;
|
||||
|
||||
void setName(String moduleType, int channel) {
|
||||
m_name = moduleType + "[" + channel + "]";
|
||||
}
|
||||
|
||||
void setName(String moduleType, int moduleNumber, int channel) {
|
||||
m_name = moduleType + "[" + moduleNumber + "," + channel + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<Object, Component> components = new WeakHashMap<>();
|
||||
private static int nextDataHandle;
|
||||
|
||||
private static Component getOrAdd(Sendable sendable) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp == null) {
|
||||
comp = new Component(sendable);
|
||||
components.put(sendable, comp);
|
||||
} else {
|
||||
if (comp.m_sendable == null) {
|
||||
comp.m_sendable = new WeakReference<>(sendable);
|
||||
}
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
|
||||
private SendableRegistry() {
|
||||
throw new UnsupportedOperationException("This is a utility class!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an object to the registry.
|
||||
*
|
||||
* @param sendable object to add
|
||||
* @param name component name
|
||||
*/
|
||||
public static synchronized void add(Sendable sendable, String name) {
|
||||
Component comp = getOrAdd(sendable);
|
||||
comp.m_name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an object to the registry.
|
||||
*
|
||||
* @param sendable object to add
|
||||
* @param moduleType A string that defines the module name in the label for the value
|
||||
* @param channel The channel number the device is plugged into
|
||||
*/
|
||||
public static synchronized void add(Sendable sendable, String moduleType, int channel) {
|
||||
Component comp = getOrAdd(sendable);
|
||||
comp.setName(moduleType, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an object to the registry.
|
||||
*
|
||||
* @param sendable object to add
|
||||
* @param moduleType A string that defines the module name in the label for the value
|
||||
* @param moduleNumber The number of the particular module type
|
||||
* @param channel The channel number the device is plugged into
|
||||
*/
|
||||
public static synchronized void add(
|
||||
Sendable sendable, String moduleType, int moduleNumber, int channel) {
|
||||
Component comp = getOrAdd(sendable);
|
||||
comp.setName(moduleType, moduleNumber, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an object to the registry.
|
||||
*
|
||||
* @param sendable object to add
|
||||
* @param subsystem subsystem name
|
||||
* @param name component name
|
||||
*/
|
||||
public static synchronized void add(Sendable sendable, String subsystem, String name) {
|
||||
Component comp = getOrAdd(sendable);
|
||||
comp.m_name = name;
|
||||
comp.m_subsystem = subsystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child object to an object. Adds the child object to the registry if it's not already
|
||||
* present.
|
||||
*
|
||||
* @param parent parent object
|
||||
* @param child child object
|
||||
*/
|
||||
public static synchronized void addChild(Sendable parent, Object child) {
|
||||
Component comp = components.get(child);
|
||||
if (comp == null) {
|
||||
comp = new Component();
|
||||
components.put(child, comp);
|
||||
}
|
||||
// comp.m_parent = new WeakReference<>(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an object from the registry.
|
||||
*
|
||||
* @param sendable object to remove
|
||||
* @return true if the object was removed; false if it was not present
|
||||
*/
|
||||
public static synchronized boolean remove(Sendable sendable) {
|
||||
Component comp = components.remove(sendable);
|
||||
if (comp != null) {
|
||||
try {
|
||||
comp.close();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return comp != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an object is in the registry.
|
||||
*
|
||||
* @param sendable object to check
|
||||
* @return True if in registry, false if not.
|
||||
*/
|
||||
public static synchronized boolean contains(Sendable sendable) {
|
||||
return components.containsKey(sendable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of an object.
|
||||
*
|
||||
* @param sendable object
|
||||
* @return Name (empty if object is not in registry)
|
||||
*/
|
||||
public static synchronized String getName(Sendable sendable) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp == null) {
|
||||
return "";
|
||||
}
|
||||
return comp.m_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of an object.
|
||||
*
|
||||
* @param sendable object
|
||||
* @param name name
|
||||
*/
|
||||
public static synchronized void setName(Sendable sendable, String name) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp != null) {
|
||||
comp.m_name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of an object with a channel number.
|
||||
*
|
||||
* @param sendable object
|
||||
* @param moduleType A string that defines the module name in the label for the value
|
||||
* @param channel The channel number the device is plugged into
|
||||
*/
|
||||
public static synchronized void setName(Sendable sendable, String moduleType, int channel) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp != null) {
|
||||
comp.setName(moduleType, channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of an object with a module and channel number.
|
||||
*
|
||||
* @param sendable object
|
||||
* @param moduleType A string that defines the module name in the label for the value
|
||||
* @param moduleNumber The number of the particular module type
|
||||
* @param channel The channel number the device is plugged into
|
||||
*/
|
||||
public static synchronized void setName(
|
||||
Sendable sendable, String moduleType, int moduleNumber, int channel) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp != null) {
|
||||
comp.setName(moduleType, moduleNumber, channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets both the subsystem name and device name of an object.
|
||||
*
|
||||
* @param sendable object
|
||||
* @param subsystem subsystem name
|
||||
* @param name device name
|
||||
*/
|
||||
public static synchronized void setName(Sendable sendable, String subsystem, String name) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp != null) {
|
||||
comp.m_name = name;
|
||||
comp.m_subsystem = subsystem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subsystem name of an object.
|
||||
*
|
||||
* @param sendable object
|
||||
* @return Subsystem name (empty if object is not in registry)
|
||||
*/
|
||||
public static synchronized String getSubsystem(Sendable sendable) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp == null) {
|
||||
return "";
|
||||
}
|
||||
return comp.m_subsystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subsystem name of an object.
|
||||
*
|
||||
* @param sendable object
|
||||
* @param subsystem subsystem name
|
||||
*/
|
||||
public static synchronized void setSubsystem(Sendable sendable, String subsystem) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp != null) {
|
||||
comp.m_subsystem = subsystem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique handle for setting/getting data with setData() and getData().
|
||||
*
|
||||
* @return Handle
|
||||
*/
|
||||
public static synchronized int getDataHandle() {
|
||||
return nextDataHandle++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates arbitrary data with an object in the registry.
|
||||
*
|
||||
* @param sendable object
|
||||
* @param handle data handle returned by getDataHandle()
|
||||
* @param data data to set
|
||||
* @return Previous data (may be null). If non-null, caller is responsible for calling close().
|
||||
*/
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
public static synchronized AutoCloseable setData(
|
||||
Sendable sendable, int handle, AutoCloseable data) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp == null) {
|
||||
return null;
|
||||
}
|
||||
AutoCloseable rv = null;
|
||||
if (comp.m_data == null) {
|
||||
comp.m_data = new AutoCloseable[handle + 1];
|
||||
} else if (handle < comp.m_data.length) {
|
||||
rv = comp.m_data[handle];
|
||||
} else {
|
||||
comp.m_data = Arrays.copyOf(comp.m_data, handle + 1);
|
||||
}
|
||||
if (comp.m_data[handle] != data) {
|
||||
if (comp.m_data[handle] != null) {
|
||||
try {
|
||||
comp.m_data[handle].close();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
comp.m_data[handle] = data;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets arbitrary data associated with an object in the registry.
|
||||
*
|
||||
* @param sendable object
|
||||
* @param handle data handle returned by getDataHandle()
|
||||
* @return data (may be null if none associated)
|
||||
*/
|
||||
public static synchronized Object getData(Sendable sendable, int handle) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp == null || comp.m_data == null || handle >= comp.m_data.length) {
|
||||
return null;
|
||||
}
|
||||
return comp.m_data[handle];
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes an object in the registry to a builder.
|
||||
*
|
||||
* @param sendable object
|
||||
* @param builder sendable builder
|
||||
*/
|
||||
public static synchronized void publish(Sendable sendable, SendableBuilder builder) {
|
||||
Component comp = getOrAdd(sendable);
|
||||
if (comp.m_builder != null) {
|
||||
try {
|
||||
comp.m_builder.close();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
comp.m_builder = builder; // clear any current builder
|
||||
sendable.initSendable(comp.m_builder);
|
||||
comp.m_builder.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates network table information from an object.
|
||||
*
|
||||
* @param sendable object
|
||||
*/
|
||||
public static synchronized void update(Sendable sendable) {
|
||||
Component comp = components.get(sendable);
|
||||
if (comp != null && comp.m_builder != null) {
|
||||
comp.m_builder.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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