[wpiutil] Enhance DataLog Java raw value support

Add support for start, length, and ByteBuffers
This commit is contained in:
Peter Johnson
2023-05-17 14:06:52 -07:00
parent 8dae5af271
commit 3a6e40a44b
6 changed files with 309 additions and 25 deletions

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.util.datalog;
import java.nio.ByteBuffer;
/**
* A data log. The log file is created immediately upon construction with a temporary filename. The
* file may be renamed at any time using the setFilename() function.
@@ -191,11 +193,49 @@ public final class DataLog implements AutoCloseable {
* Appends a raw record to the log.
*
* @param entry Entry index, as returned by start()
* @param data Byte array to record
* @param data Byte array to record; will send entire array contents
* @param timestamp Time stamp (0 to indicate now)
*/
public void appendRaw(int entry, byte[] data, long timestamp) {
DataLogJNI.appendRaw(m_impl, entry, data, timestamp);
appendRaw(entry, data, 0, data.length, timestamp);
}
/**
* Appends a record to the log.
*
* @param entry Entry index, as returned by start()
* @param data Byte array to record
* @param start Start position of data (in byte array)
* @param len Length of data (must be less than or equal to data.length - start)
* @param timestamp Time stamp (0 to indicate now)
*/
public void appendRaw(int entry, byte[] data, int start, int len, long timestamp) {
DataLogJNI.appendRaw(m_impl, entry, data, start, len, timestamp);
}
/**
* Appends a record to the log.
*
* @param entry Entry index, as returned by start()
* @param data Buffer to record; will send from data.position() to data.limit()
* @param timestamp Time stamp (0 to indicate now)
*/
public void appendRaw(int entry, ByteBuffer data, long timestamp) {
int pos = data.position();
appendRaw(entry, data, pos, data.limit() - pos, timestamp);
}
/**
* Appends a record to the log.
*
* @param entry Entry index, as returned by start()
* @param data Buffer to record
* @param start Start position of data (in buffer)
* @param len Length of data (must be less than or equal to data.capacity() - start)
* @param timestamp Time stamp (0 to indicate now)
*/
public void appendRaw(int entry, ByteBuffer data, int start, int len, long timestamp) {
DataLogJNI.appendRaw(m_impl, entry, data, start, len, timestamp);
}
@Override

View File

@@ -5,6 +5,7 @@
package edu.wpi.first.util.datalog;
import edu.wpi.first.util.WPIUtilJNI;
import java.nio.ByteBuffer;
public class DataLogJNI extends WPIUtilJNI {
static native long create(String dir, String filename, double period, String extraHeader);
@@ -25,7 +26,30 @@ public class DataLogJNI extends WPIUtilJNI {
static native void close(long impl);
static native void appendRaw(long impl, int entry, byte[] data, long timestamp);
static native void appendRaw(
long impl, int entry, byte[] data, int start, int len, long timestamp);
static void appendRaw(long impl, int entry, ByteBuffer data, int start, int len, long timestamp) {
if (data.isDirect()) {
if (start < 0) {
throw new IndexOutOfBoundsException("start must be >= 0");
}
if (len < 0) {
throw new IndexOutOfBoundsException("len must be >= 0");
}
if ((start + len) > data.capacity()) {
throw new IndexOutOfBoundsException("start + len must be smaller than buffer capacity");
}
appendRawBuffer(impl, entry, data, start, len, timestamp);
} else if (data.hasArray()) {
appendRaw(impl, entry, data.array(), data.arrayOffset() + start, len, timestamp);
} else {
throw new UnsupportedOperationException("ByteBuffer must be direct or have a backing array");
}
}
private static native void appendRawBuffer(
long impl, int entry, ByteBuffer data, int start, int len, long timestamp);
static native void appendBoolean(long impl, int entry, boolean value, long timestamp);

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.util.datalog;
import java.nio.ByteBuffer;
/** Log raw byte array values. */
public class RawLogEntry extends DataLogEntry {
public static final String kDataType = "raw";
@@ -35,7 +37,7 @@ public class RawLogEntry extends DataLogEntry {
/**
* Appends a record to the log.
*
* @param value Value to record
* @param value Value to record; will send entire array contents
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(byte[] value, long timestamp) {
@@ -45,9 +47,74 @@ public class RawLogEntry extends DataLogEntry {
/**
* Appends a record to the log.
*
* @param value Value to record
* @param value Value to record; will send entire array contents
*/
public void append(byte[] value) {
m_log.appendRaw(m_entry, value, 0);
append(value, 0);
}
/**
* Appends a record to the log.
*
* @param value Data to record
* @param start Start position of data (in byte array)
* @param len Length of data (must be less than or equal to value.length - offset)
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(byte[] value, int start, int len, long timestamp) {
m_log.appendRaw(m_entry, value, start, len, timestamp);
}
/**
* Appends a record to the log.
*
* @param value Data to record
* @param start Start position of data (in byte array)
* @param len Length of data (must be less than or equal to value.length - offset)
*/
public void append(byte[] value, int start, int len) {
append(value, start, len, 0);
}
/**
* Appends a record to the log.
*
* @param value Data to record; will send from value.position() to value.capacity()
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(ByteBuffer value, long timestamp) {
m_log.appendRaw(m_entry, value, timestamp);
}
/**
* Appends a record to the log.
*
* @param value Data to record; will send from value.position() to value.capacity()
*/
public void append(ByteBuffer value) {
append(value, 0);
}
/**
* Appends a record to the log.
*
* @param value Data to record
* @param start Start position of data (in value buffer)
* @param len Length of data (must be less than or equal to value.length - offset)
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(ByteBuffer value, int start, int len, long timestamp) {
m_log.appendRaw(m_entry, value, start, len, timestamp);
}
/**
* Appends a record to the log.
*
* @param value Data to record
* @param start Start position of data (in value buffer)
* @param len Length of data (must be less than or equal to value.length - offset)
*/
public void append(ByteBuffer value, int start, int len) {
append(value, start, len, 0);
}
}

View File

@@ -4,7 +4,9 @@
#include <jni.h>
#include "WPIUtilJNI.h"
#include "edu_wpi_first_util_datalog_DataLogJNI.h"
#include "fmt/format.h"
#include "wpi/DataLog.h"
#include "wpi/jni_util.h"
@@ -23,6 +25,18 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_create
(JNIEnv* env, jclass, jstring dir, jstring filename, jdouble period,
jstring extraHeader)
{
if (!dir) {
wpi::ThrowNullPointerException(env, "dir is null");
return 0;
}
if (!filename) {
wpi::ThrowNullPointerException(env, "filename is null");
return 0;
}
if (!extraHeader) {
wpi::ThrowNullPointerException(env, "extraHeader is null");
return 0;
}
return reinterpret_cast<jlong>(new DataLog{JStringRef{env, dir},
JStringRef{env, filename}, period,
JStringRef{env, extraHeader}});
@@ -38,6 +52,11 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_setFilename
(JNIEnv* env, jclass, jlong impl, jstring filename)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
if (!filename) {
wpi::ThrowNullPointerException(env, "filename is null");
return;
}
reinterpret_cast<DataLog*>(impl)->SetFilename(JStringRef{env, filename});
@@ -50,9 +69,10 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_setFilename
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_flush
(JNIEnv*, jclass, jlong impl)
(JNIEnv* env, jclass, jlong impl)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->Flush();
@@ -65,9 +85,10 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_flush
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_pause
(JNIEnv*, jclass, jlong impl)
(JNIEnv* env, jclass, jlong impl)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->Pause();
@@ -80,9 +101,10 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_pause
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_resume
(JNIEnv*, jclass, jlong impl)
(JNIEnv* env, jclass, jlong impl)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->Resume();
@@ -99,6 +121,7 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_start
jstring metadata, jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return 0;
}
return reinterpret_cast<DataLog*>(impl)->Start(
@@ -113,9 +136,10 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_start
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_finish
(JNIEnv*, jclass, jlong impl, jint entry, jlong timestamp)
(JNIEnv* env, jclass, jlong impl, jint entry, jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->Finish(entry, timestamp);
@@ -132,6 +156,7 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_setMetadata
jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->SetMetadata(
@@ -153,21 +178,73 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_close
/*
* Class: edu_wpi_first_util_datalog_DataLogJNI
* Method: appendRaw
* Signature: (JI[BJ)V
* Signature: (JI[BIIJ)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_appendRaw
(JNIEnv* env, jclass, jlong impl, jint entry, jbyteArray value,
jlong timestamp)
(JNIEnv* env, jclass, jlong impl, jint entry, jbyteArray value, jint start,
jint length, jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
if (!value) {
wpi::ThrowNullPointerException(env, "value is null");
return;
}
if (start < 0) {
wpi::ThrowIndexOobException(env, "start must be >= 0");
return;
}
if (length < 0) {
wpi::ThrowIndexOobException(env, "length must be >= 0");
return;
}
CriticalJByteArrayRef cvalue{env, value};
if (static_cast<unsigned int>(start + length) > cvalue.size()) {
wpi::ThrowIndexOobException(
env, "start + len must be smaller than array length");
return;
}
JByteArrayRef cvalue{env, value};
reinterpret_cast<DataLog*>(impl)->AppendRaw(
entry,
{reinterpret_cast<const uint8_t*>(cvalue.array().data()), cvalue.size()},
timestamp);
entry, cvalue.uarray().subspan(start, length), timestamp);
}
/*
* Class: edu_wpi_first_util_datalog_DataLogJNI
* Method: appendRawBuffer
* Signature: (JILjava/lang/Object;IIJ)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_appendRawBuffer
(JNIEnv* env, jclass, jlong impl, jint entry, jobject value, jint start,
jint length, jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
if (!value) {
wpi::ThrowNullPointerException(env, "value is null");
return;
}
if (start < 0) {
wpi::ThrowIndexOobException(env, "start must be >= 0");
return;
}
if (length < 0) {
wpi::ThrowIndexOobException(env, "length must be >= 0");
return;
}
JByteArrayRef cvalue{env, value, start + length};
if (!cvalue) {
wpi::ThrowIllegalArgumentException(env,
"value must be a native ByteBuffer");
return;
}
reinterpret_cast<DataLog*>(impl)->AppendRaw(
entry, cvalue.uarray().subspan(start, length), timestamp);
}
/*
@@ -177,9 +254,10 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendRaw
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_appendBoolean
(JNIEnv*, jclass, jlong impl, jint entry, jboolean value, jlong timestamp)
(JNIEnv* env, jclass, jlong impl, jint entry, jboolean value, jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->AppendBoolean(entry, value, timestamp);
@@ -192,9 +270,10 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendBoolean
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_appendInteger
(JNIEnv*, jclass, jlong impl, jint entry, jlong value, jlong timestamp)
(JNIEnv* env, jclass, jlong impl, jint entry, jlong value, jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->AppendInteger(entry, value, timestamp);
@@ -207,9 +286,10 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendInteger
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_appendFloat
(JNIEnv*, jclass, jlong impl, jint entry, jfloat value, jlong timestamp)
(JNIEnv* env, jclass, jlong impl, jint entry, jfloat value, jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->AppendFloat(entry, value, timestamp);
@@ -222,9 +302,10 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendFloat
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_datalog_DataLogJNI_appendDouble
(JNIEnv*, jclass, jlong impl, jint entry, jdouble value, jlong timestamp)
(JNIEnv* env, jclass, jlong impl, jint entry, jdouble value, jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->AppendDouble(entry, value, timestamp);
@@ -240,6 +321,7 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendString
(JNIEnv* env, jclass, jlong impl, jint entry, jstring value, jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
reinterpret_cast<DataLog*>(impl)->AppendString(entry, JStringRef{env, value},
@@ -257,6 +339,11 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendBooleanArray
jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
if (!value) {
wpi::ThrowNullPointerException(env, "value is null");
return;
}
reinterpret_cast<DataLog*>(impl)->AppendBooleanArray(
@@ -274,6 +361,11 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendIntegerArray
jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
if (!value) {
wpi::ThrowNullPointerException(env, "value is null");
return;
}
JLongArrayRef jarr{env, value};
@@ -304,6 +396,11 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendFloatArray
jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
if (!value) {
wpi::ThrowNullPointerException(env, "value is null");
return;
}
reinterpret_cast<DataLog*>(impl)->AppendFloatArray(
@@ -321,6 +418,11 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendDoubleArray
jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
if (!value) {
wpi::ThrowNullPointerException(env, "value is null");
return;
}
reinterpret_cast<DataLog*>(impl)->AppendDoubleArray(
@@ -338,6 +440,11 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendStringArray
jlong timestamp)
{
if (impl == 0) {
wpi::ThrowNullPointerException(env, "impl is null");
return;
}
if (!value) {
wpi::ThrowNullPointerException(env, "value is null");
return;
}
size_t len = env->GetArrayLength(value);
@@ -347,6 +454,8 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_appendStringArray
JLocal<jstring> elem{
env, static_cast<jstring>(env->GetObjectArrayElement(value, i))};
if (!elem) {
wpi::ThrowNullPointerException(
env, fmt::format("string at element {} is null", i));
return;
}
arr.emplace_back(JStringRef{env, elem}.str());

View File

@@ -2,6 +2,8 @@
// 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.
#include "WPIUtilJNI.h"
#include <jni.h>
#include "edu_wpi_first_util_WPIUtilJNI.h"
@@ -15,7 +17,28 @@ using namespace wpi::java;
static bool mockTimeEnabled = false;
static uint64_t mockNow = 0;
static JException illegalArgEx;
static JException indexOobEx;
static JException interruptedEx;
static JException nullPointerEx;
static const JExceptionInit exceptions[] = {
{"java/lang/IllegalArgumentException", &illegalArgEx},
{"java/lang/IndexOutOfBoundsException", &indexOobEx},
{"java/lang/InterruptedException", &interruptedEx},
{"java/lang/NullPointerException", &nullPointerEx}};
void wpi::ThrowIllegalArgumentException(JNIEnv* env, std::string_view msg) {
illegalArgEx.Throw(env, msg);
}
void wpi::ThrowIndexOobException(JNIEnv* env, std::string_view msg) {
indexOobEx.Throw(env, msg);
}
void wpi::ThrowNullPointerException(JNIEnv* env, std::string_view msg) {
nullPointerEx.Throw(env, msg);
}
extern "C" {
@@ -25,9 +48,11 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
return JNI_ERR;
}
interruptedEx = JException(env, "java/lang/InterruptedException");
if (!interruptedEx) {
return JNI_ERR;
for (auto& c : exceptions) {
*c.cls = JException(env, c.name);
if (!*c.cls) {
return JNI_ERR;
}
}
return JNI_VERSION_1_6;
@@ -39,7 +64,9 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
return;
}
interruptedEx.free(env);
for (auto& c : exceptions) {
c.cls->free(env);
}
}
/*

View File

@@ -0,0 +1,17 @@
// 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.
#pragma once
#include <jni.h>
#include <string_view>
namespace wpi {
void ThrowIllegalArgumentException(JNIEnv* env, std::string_view msg);
void ThrowIndexOobException(JNIEnv* env, std::string_view msg);
void ThrowNullPointerException(JNIEnv* env, std::string_view msg);
} // namespace wpi