diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLog.java b/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLog.java index 7fd8636cad..1003f80a51 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLog.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLog.java @@ -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 diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLogJNI.java b/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLogJNI.java index 724b3d96fc..85c85251b1 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLogJNI.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLogJNI.java @@ -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); diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/RawLogEntry.java b/wpiutil/src/main/java/edu/wpi/first/util/datalog/RawLogEntry.java index 160f734e55..972fc03cfa 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/RawLogEntry.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/datalog/RawLogEntry.java @@ -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); } } diff --git a/wpiutil/src/main/native/cpp/jni/DataLogJNI.cpp b/wpiutil/src/main/native/cpp/jni/DataLogJNI.cpp index 997b90b158..faf910dc65 100644 --- a/wpiutil/src/main/native/cpp/jni/DataLogJNI.cpp +++ b/wpiutil/src/main/native/cpp/jni/DataLogJNI.cpp @@ -4,7 +4,9 @@ #include +#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(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(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(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(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(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(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(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(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(start + length) > cvalue.size()) { + wpi::ThrowIndexOobException( + env, "start + len must be smaller than array length"); return; } - JByteArrayRef cvalue{env, value}; reinterpret_cast(impl)->AppendRaw( - entry, - {reinterpret_cast(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(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(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(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(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(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(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(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(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(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 elem{ env, static_cast(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()); diff --git a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp index 7accd3075b..b9e98970fc 100644 --- a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp +++ b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp @@ -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 #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); + } } /* diff --git a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.h b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.h new file mode 100644 index 0000000000..541064f2b3 --- /dev/null +++ b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.h @@ -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 + +#include + +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