diff --git a/ntcore/src/generate/cpp/jni/types_jni.cpp.jinja b/ntcore/src/generate/cpp/jni/types_jni.cpp.jinja index a1052784dc..c26adc2cc2 100644 --- a/ntcore/src/generate/cpp/jni/types_jni.cpp.jinja +++ b/ntcore/src/generate/cpp/jni/types_jni.cpp.jinja @@ -22,6 +22,8 @@ static JClass timestamped{{ t.TypeName }}Cls; static JClass {{ t.jni.jtype }}Cls; {%- endif %} {%- endfor %} +static JException illegalArgEx; +static JException indexOobEx; static JException nullPointerEx; static const JClassInit classes[] = { @@ -36,6 +38,8 @@ static const JClassInit classes[] = { }; static const JExceptionInit exceptions[] = { + {"java/lang/IllegalArgumentException", &illegalArgEx}, + {"java/lang/IndexOutOfBoundsException", &indexOobEx}, {"java/lang/NullPointerException", &nullPointerEx}, }; @@ -65,6 +69,9 @@ void JNI_UnloadTypes(JNIEnv* env) { for (auto& c : classes) { c.cls->free(env); } + for (auto& c : exceptions) { + c.cls->free(env); + } } } // namespace nt @@ -185,7 +192,65 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_readQueueValues{{ t.TypeName } { return {{ t.jni.ToJavaArray }}(env, nt::ReadQueueValues{{ t.TypeName }}(subentry)); } +{% if t.TypeName == "Raw" %} +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setRaw + * Signature: (IJ[BII)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_first_networktables_NetworkTablesJNI_setRaw + (JNIEnv* env, jclass, jint entry, jlong time, jbyteArray value, jint start, jint len) +{ + if (!value) { + nullPointerEx.Throw(env, "value is null"); + return false; + } + if (start < 0) { + indexOobEx.Throw(env, "start must be >= 0"); + return false; + } + if (len < 0) { + indexOobEx.Throw(env, "len must be >= 0"); + return false; + } + CriticalJByteArrayRef cvalue{env, value}; + if (static_cast(start + len) > cvalue.size()) { + indexOobEx.Throw(env, "start + len must be smaller than array length"); + return false; + } + return nt::SetRaw(entry, cvalue.uarray().subspan(start, len), time); +} +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setRawBuffer + * Signature: (IJLjava/nio/ByteBuffer;II)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_first_networktables_NetworkTablesJNI_setRawBuffer + (JNIEnv* env, jclass, jint entry, jlong time, jobject value, jint start, jint len) +{ + if (!value) { + nullPointerEx.Throw(env, "value is null"); + return false; + } + if (start < 0) { + indexOobEx.Throw(env, "start must be >= 0"); + return false; + } + if (len < 0) { + indexOobEx.Throw(env, "len must be >= 0"); + return false; + } + JByteArrayRef cvalue{env, value, start + len}; + if (!cvalue) { + illegalArgEx.Throw(env, "value must be a native ByteBuffer"); + return false; + } + return nt::SetRaw(entry, cvalue.uarray().subspan(start, len), time); +} +{% else %} /* * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: set{{ t.TypeName }} @@ -203,7 +268,7 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_set{{ t.TypeName }} {%- endif %} return nt::Set{{ t.TypeName }}(entry, {{ t.jni.FromJavaBegin }}value{{ t.jni.FromJavaEnd }}, time); } - +{% endif %} /* * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: get{{ t.TypeName }} @@ -223,7 +288,65 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_get{{ t.TypeName }} return nt::Get{{ t.TypeName }}(entry, defaultValue); {%- endif %} } +{% if t.TypeName == "Raw" %} +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setDefaultRaw + * Signature: (IJ[BII)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultRaw + (JNIEnv* env, jclass, jint entry, jlong, jbyteArray defaultValue, jint start, jint len) +{ + if (!defaultValue) { + nullPointerEx.Throw(env, "value is null"); + return false; + } + if (start < 0) { + indexOobEx.Throw(env, "start must be >= 0"); + return false; + } + if (len < 0) { + indexOobEx.Throw(env, "len must be >= 0"); + return false; + } + CriticalJByteArrayRef cvalue{env, defaultValue}; + if (static_cast(start + len) > cvalue.size()) { + indexOobEx.Throw(env, "start + len must be smaller than array length"); + return false; + } + return nt::SetDefaultRaw(entry, cvalue.uarray().subspan(start, len)); +} +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setDefaultRawBuffer + * Signature: (IJLjava/nio/ByteBuffer;II)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultRawBuffer + (JNIEnv* env, jclass, jint entry, jlong, jobject defaultValue, jint start, jint len) +{ + if (!defaultValue) { + nullPointerEx.Throw(env, "value is null"); + return false; + } + if (start < 0) { + indexOobEx.Throw(env, "start must be >= 0"); + return false; + } + if (len < 0) { + indexOobEx.Throw(env, "len must be >= 0"); + return false; + } + JByteArrayRef cvalue{env, defaultValue, start + len}; + if (!cvalue) { + illegalArgEx.Throw(env, "value must be a native ByteBuffer"); + return false; + } + return nt::SetDefaultRaw(entry, cvalue.uarray().subspan(start, len)); +} +{% else %} /* * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setDefault{{ t.TypeName }} @@ -241,5 +364,6 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefault{{ t.TypeName }} {%- endif %} return nt::SetDefault{{ t.TypeName }}(entry, {{ t.jni.FromJavaBegin }}defaultValue{{ t.jni.FromJavaEnd }}); } +{% endif %} {% endfor %} } // extern "C" diff --git a/ntcore/src/generate/java/EntryImpl.java.jinja b/ntcore/src/generate/java/EntryImpl.java.jinja index 43b31e4a43..b7432a7e31 100644 --- a/ntcore/src/generate/java/EntryImpl.java.jinja +++ b/ntcore/src/generate/java/EntryImpl.java.jinja @@ -3,7 +3,9 @@ // the WPILib BSD license file in the root directory of this project. package edu.wpi.first.networktables; - +{% if TypeName == "Raw" %} +import java.nio.ByteBuffer; +{% endif %} /** NetworkTables {{ TypeName }} implementation. */ @SuppressWarnings("PMD.ArrayIsStoredDirectly") final class {{ TypeName }}EntryImpl extends EntryBase implements {{ TypeName }}Entry { @@ -54,7 +56,27 @@ final class {{ TypeName }}EntryImpl extends EntryBase implements {{ TypeName }}E public {{ java.ValueType }}[] readQueueValues() { return NetworkTablesJNI.readQueueValues{{ TypeName }}(m_handle); } +{% if TypeName == "Raw" %} + @Override + public void set(byte[] value, int start, int len, long time) { + NetworkTablesJNI.setRaw(m_handle, time, value, start, len); + } + @Override + public void set(ByteBuffer value, int start, int len, long time) { + NetworkTablesJNI.setRaw(m_handle, time, value, start, len); + } + + @Override + public void setDefault(byte[] value, int start, int len) { + NetworkTablesJNI.setDefaultRaw(m_handle, 0, value, start, len); + } + + @Override + public void setDefault(ByteBuffer value, int start, int len) { + NetworkTablesJNI.setDefaultRaw(m_handle, 0, value, start, len); + } +{% else %} @Override public void set({{ java.ValueType }} value, long time) { NetworkTablesJNI.set{{ TypeName }}(m_handle, time, value); @@ -64,7 +86,7 @@ final class {{ TypeName }}EntryImpl extends EntryBase implements {{ TypeName }}E public void setDefault({{ java.ValueType }} value) { NetworkTablesJNI.setDefault{{ TypeName }}(m_handle, 0, value); } - +{% endif %} @Override public void unpublish() { NetworkTablesJNI.unpublish(m_handle); diff --git a/ntcore/src/generate/java/GenericEntryImpl.java.jinja b/ntcore/src/generate/java/GenericEntryImpl.java.jinja index e4296d7d9c..29666bb79e 100644 --- a/ntcore/src/generate/java/GenericEntryImpl.java.jinja +++ b/ntcore/src/generate/java/GenericEntryImpl.java.jinja @@ -4,6 +4,8 @@ package edu.wpi.first.networktables; +import java.nio.ByteBuffer; + /** NetworkTables generic implementation. */ final class GenericEntryImpl extends EntryBase implements GenericEntry { /** @@ -140,6 +142,33 @@ final class GenericEntryImpl extends EntryBase implements GenericEntry { } } {% for t in types %} +{% if t.TypeName == "Raw" %} + /** + * Sets the entry's value. + * + * @param value the value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + * @return False if the entry exists with a different type + */ + @Override + public boolean setRaw(byte[] value, int start, int len, long time) { + return NetworkTablesJNI.setRaw(m_handle, time, value, start, len); + } + + /** + * Sets the entry's value. + * + * @param value the value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + * @return False if the entry exists with a different type + */ + @Override + public boolean setRaw(ByteBuffer value, int start, int len, long time) { + return NetworkTablesJNI.setRaw(m_handle, time, value, start, len); + } +{% else %} /** * Sets the entry's value. * @@ -150,6 +179,7 @@ final class GenericEntryImpl extends EntryBase implements GenericEntry { public boolean set{{ t.TypeName }}({{ t.java.ValueType }} value, long time) { return NetworkTablesJNI.set{{ t.TypeName }}(m_handle, time, value); } +{% endif -%} {% if t.java.WrapValueType %} /** * Sets the entry's value. @@ -265,6 +295,33 @@ final class GenericEntryImpl extends EntryBase implements GenericEntry { } } {% for t in types %} +{% if t.TypeName == "Raw" %} + /** + * Sets the entry's value if it does not exist. + * + * @param defaultValue the default value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + * @return False if the entry exists with a different type + */ + @Override + public boolean setDefaultRaw(byte[] defaultValue, int start, int len) { + return NetworkTablesJNI.setDefaultRaw(m_handle, 0, defaultValue, start, len); + } + + /** + * Sets the entry's value if it does not exist. + * + * @param defaultValue the default value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + * @return False if the entry exists with a different type + */ + @Override + public boolean setDefaultRaw(ByteBuffer defaultValue, int start, int len) { + return NetworkTablesJNI.setDefaultRaw(m_handle, 0, defaultValue, start, len); + } +{% else %} /** * Sets the entry's value if it does not exist. * @@ -275,6 +332,7 @@ final class GenericEntryImpl extends EntryBase implements GenericEntry { public boolean setDefault{{ t.TypeName }}({{ t.java.ValueType }} defaultValue) { return NetworkTablesJNI.setDefault{{ t.TypeName }}(m_handle, 0, defaultValue); } +{% endif -%} {% if t.java.WrapValueType %} /** * Sets the entry's value if it does not exist. diff --git a/ntcore/src/generate/java/GenericPublisher.java.jinja b/ntcore/src/generate/java/GenericPublisher.java.jinja index d747f1751a..881aba63d5 100644 --- a/ntcore/src/generate/java/GenericPublisher.java.jinja +++ b/ntcore/src/generate/java/GenericPublisher.java.jinja @@ -4,6 +4,7 @@ package edu.wpi.first.networktables; +import java.nio.ByteBuffer; import java.util.function.Consumer; /** NetworkTables generic publisher. */ @@ -54,7 +55,86 @@ public interface GenericPublisher extends Publisher, Consumer default boolean set{{ t.TypeName }}({{ t.java.ValueType }} value) { return set{{ t.TypeName }}(value, 0); } +{% if t.TypeName == "Raw" %} + /** + * Publish a new value. + * + * @param value value to publish + * @return False if the topic already exists with a different type + */ + default boolean setRaw(ByteBuffer value) { + return setRaw(value, 0); + } + /** + * Publish a new value. + * + * @param value value to publish + * @param time timestamp; 0 indicates current NT time should be used + * @return False if the topic already exists with a different type + */ + default boolean setRaw(byte[] value, long time) { + return setRaw(value, 0, value.length, time); + } + + /** + * Publish a new value. + * + * @param value value to publish; will send from value.position() to value.limit() + * @param time timestamp; 0 indicates current NT time should be used + * @return False if the topic already exists with a different type + */ + default boolean setRaw(ByteBuffer value, long time) { + int pos = value.position(); + return setRaw(value, pos, value.limit() - pos, time); + } + + /** + * Publish a new value. + * + * @param value value to publish + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + * @return False if the topic already exists with a different type + */ + default boolean setRaw(byte[] value, int start, int len) { + return setRaw(value, start, len, 0); + } + + /** + * Publish a new value. + * + * @param value value to publish + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + * @param time timestamp; 0 indicates current NT time should be used + * @return False if the topic already exists with a different type + */ + boolean setRaw(byte[] value, int start, int len, long time); + + /** + * Publish a new value. + * + * @param value value to publish + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + * @return False if the topic already exists with a different type + */ + default boolean setRaw(ByteBuffer value, int start, int len) { + return setRaw(value, start, len, 0); + } + + /** + * Publish a new value. + * + * @param value value to publish + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + * @param time timestamp; 0 indicates current NT time should be used + * @return False if the topic already exists with a different type + */ + boolean setRaw(ByteBuffer value, int start, int len, long time); +{% else %} /** * Publish a new value. * @@ -63,6 +143,7 @@ public interface GenericPublisher extends Publisher, Consumer * @return False if the topic already exists with a different type */ boolean set{{ t.TypeName }}({{ t.java.ValueType }} value, long time); +{% endif -%} {% if t.java.WrapValueType %} /** * Publish a new value. @@ -101,6 +182,49 @@ public interface GenericPublisher extends Publisher, Consumer */ boolean setDefaultValue(Object defaultValue); {% for t in types %} +{% if t.TypeName == "Raw" %} + /** + * Sets the entry's value if it does not exist. + * + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + default boolean setDefaultRaw(byte[] defaultValue) { + return setDefaultRaw(defaultValue, 0, defaultValue.length); + } + + /** + * Sets the entry's value if it does not exist. + * + * @param defaultValue the default value to set; will send from defaultValue.position() to + * defaultValue.limit() + * @return False if the entry exists with a different type + */ + default boolean setDefaultRaw(ByteBuffer defaultValue) { + int pos = defaultValue.position(); + return setDefaultRaw(defaultValue, pos, defaultValue.limit() - pos); + } + + /** + * Sets the entry's value if it does not exist. + * + * @param defaultValue the default value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + * @return False if the entry exists with a different type + */ + boolean setDefaultRaw(byte[] defaultValue, int start, int len); + + /** + * Sets the entry's value if it does not exist. + * + * @param defaultValue the default value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + * @return False if the entry exists with a different type + */ + boolean setDefaultRaw(ByteBuffer defaultValue, int start, int len); +{% else %} /** * Sets the entry's value if it does not exist. * @@ -108,6 +232,7 @@ public interface GenericPublisher extends Publisher, Consumer * @return False if the entry exists with a different type */ boolean setDefault{{ t.TypeName }}({{ t.java.ValueType }} defaultValue); +{% endif -%} {% if t.java.WrapValueType %} boolean setDefault{{ t.TypeName }}({{ t.java.WrapValueType }} defaultValue); {% endif -%} diff --git a/ntcore/src/generate/java/NetworkTableEntry.java.jinja b/ntcore/src/generate/java/NetworkTableEntry.java.jinja index 783ec6fac1..7ee7d1f8d0 100644 --- a/ntcore/src/generate/java/NetworkTableEntry.java.jinja +++ b/ntcore/src/generate/java/NetworkTableEntry.java.jinja @@ -4,6 +4,8 @@ package edu.wpi.first.networktables; +import java.nio.ByteBuffer; + /** * NetworkTables Entry. * @@ -310,6 +312,42 @@ public final class NetworkTableEntry implements Publisher, Subscriber { public boolean setDefault{{ t.TypeName }}({{ t.java.ValueType }} defaultValue) { return NetworkTablesJNI.setDefault{{ t.TypeName }}(m_handle, 0, defaultValue); } +{% if t.TypeName == "Raw" %} + /** + * Sets the entry's value if it does not exist. + * + * @param defaultValue the default value to set; will send from defaultValue.position() to + * defaultValue.capacity() + * @return False if the entry exists with a different type + */ + public boolean setDefaultRaw(ByteBuffer defaultValue) { + return NetworkTablesJNI.setDefaultRaw(m_handle, 0, defaultValue); + } + + /** + * Sets the entry's value if it does not exist. + * + * @param defaultValue the default value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + * @return False if the entry exists with a different type + */ + public boolean setDefaultRaw(byte[] defaultValue, int start, int len) { + return NetworkTablesJNI.setDefaultRaw(m_handle, 0, defaultValue, start, len); + } + + /** + * Sets the entry's value if it does not exist. + * + * @param defaultValue the default value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + * @return False if the entry exists with a different type + */ + public boolean setDefaultRaw(ByteBuffer defaultValue, int start, int len) { + return NetworkTablesJNI.setDefaultRaw(m_handle, 0, defaultValue, start, len); + } +{% endif -%} {% if t.java.WrapValueType %} /** * Sets the entry's value if it does not exist. @@ -427,6 +465,41 @@ public final class NetworkTableEntry implements Publisher, Subscriber { public boolean set{{ t.TypeName }}({{ t.java.ValueType }} value) { return NetworkTablesJNI.set{{ t.TypeName }}(m_handle, 0, value); } +{% if t.TypeName == "Raw" %} + /** + * Sets the entry's value. + * + * @param value the value to set; will send from value.position() to value.capacity() + * @return False if the entry exists with a different type + */ + public boolean setRaw(ByteBuffer value) { + return NetworkTablesJNI.setRaw(m_handle, 0, value); + } + + /** + * Sets the entry's value. + * + * @param value the value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + * @return False if the entry exists with a different type + */ + public boolean setRaw(byte[] value, int start, int len) { + return NetworkTablesJNI.setRaw(m_handle, 0, value, start, len); + } + + /** + * Sets the entry's value. + * + * @param value the value to set + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + * @return False if the entry exists with a different type + */ + public boolean setRaw(ByteBuffer value, int start, int len) { + return NetworkTablesJNI.setRaw(m_handle, 0, value, start, len); + } +{% endif -%} {% if t.java.WrapValueType %} /** * Sets the entry's value. diff --git a/ntcore/src/generate/java/NetworkTablesJNI.java.jinja b/ntcore/src/generate/java/NetworkTablesJNI.java.jinja index bc748c60e4..6ff9c1627b 100644 --- a/ntcore/src/generate/java/NetworkTablesJNI.java.jinja +++ b/ntcore/src/generate/java/NetworkTablesJNI.java.jinja @@ -7,6 +7,7 @@ package edu.wpi.first.networktables; import edu.wpi.first.util.RuntimeLoader; import edu.wpi.first.util.datalog.DataLog; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.OptionalLong; import java.util.concurrent.atomic.AtomicBoolean; @@ -182,12 +183,77 @@ public final class NetworkTablesJNI { public static native Timestamped{{ t.TypeName }}[] readQueue{{ t.TypeName }}(int subentry); public static native {{ t.java.ValueType }}[] readQueueValues{{ t.TypeName }}(int subentry); +{% if t.TypeName == "Raw" %} + public static boolean setRaw(int entry, long time, byte[] value) { + return setRaw(entry, time, value, 0, value.length); + } + public static native boolean setRaw(int entry, long time, byte[] value, int start, int len); + + public static boolean setRaw(int entry, long time, ByteBuffer value) { + int pos = value.position(); + return setRaw(entry, time, value, pos, value.capacity() - pos); + } + + public static boolean setRaw(int entry, long time, ByteBuffer value, int start, int len) { + if (value.isDirect()) { + if (start < 0) { + throw new IndexOutOfBoundsException("start must be >= 0"); + } + if (len < 0) { + throw new IndexOutOfBoundsException("len must be >= 0"); + } + if ((start + len) > value.capacity()) { + throw new IndexOutOfBoundsException("start + len must be smaller than buffer capacity"); + } + return setRawBuffer(entry, time, value, start, len); + } else if (value.hasArray()) { + return setRaw(entry, time, value.array(), value.arrayOffset() + start, len); + } else { + throw new UnsupportedOperationException("ByteBuffer must be direct or have a backing array"); + } + } + + private static native boolean setRawBuffer(int entry, long time, ByteBuffer value, int start, int len); +{% else %} public static native boolean set{{ t.TypeName }}(int entry, long time, {{ t.java.ValueType }} value); - +{% endif %} public static native {{ t.java.ValueType }} get{{ t.TypeName }}(int entry, {{ t.java.ValueType }} defaultValue); +{% if t.TypeName == "Raw" %} + public static boolean setDefaultRaw(int entry, long time, byte[] defaultValue) { + return setDefaultRaw(entry, time, defaultValue, 0, defaultValue.length); + } + public static native boolean setDefaultRaw(int entry, long time, byte[] defaultValue, int start, int len); + + public static boolean setDefaultRaw(int entry, long time, ByteBuffer defaultValue) { + int pos = defaultValue.position(); + return setDefaultRaw(entry, time, defaultValue, pos, defaultValue.limit() - pos); + } + + public static boolean setDefaultRaw(int entry, long time, ByteBuffer defaultValue, int start, int len) { + if (defaultValue.isDirect()) { + if (start < 0) { + throw new IndexOutOfBoundsException("start must be >= 0"); + } + if (len < 0) { + throw new IndexOutOfBoundsException("len must be >= 0"); + } + if ((start + len) > defaultValue.capacity()) { + throw new IndexOutOfBoundsException("start + len must be smaller than buffer capacity"); + } + return setDefaultRawBuffer(entry, time, defaultValue, start, len); + } else if (defaultValue.hasArray()) { + return setDefaultRaw(entry, time, defaultValue.array(), defaultValue.arrayOffset() + start, len); + } else { + throw new UnsupportedOperationException("ByteBuffer must be direct or have a backing array"); + } + } + + private static native boolean setDefaultRawBuffer(int entry, long time, ByteBuffer defaultValue, int start, int len); +{% else %} public static native boolean setDefault{{ t.TypeName }}(int entry, long time, {{ t.java.ValueType }} defaultValue); +{% endif %} {% endfor %} public static native NetworkTableValue[] readQueueValue(int subentry); diff --git a/ntcore/src/generate/java/Publisher.java.jinja b/ntcore/src/generate/java/Publisher.java.jinja index 19ead36b6d..a403d910fc 100644 --- a/ntcore/src/generate/java/Publisher.java.jinja +++ b/ntcore/src/generate/java/Publisher.java.jinja @@ -4,6 +4,9 @@ package edu.wpi.first.networktables; +{% if TypeName == "Raw" %} +import java.nio.ByteBuffer; +{% endif -%} import {{ java.ConsumerFunctionPackage|default('java.util.function') }}.{{ java.FunctionTypePrefix }}Consumer; /** NetworkTables {{ TypeName }} publisher. */ @@ -25,6 +28,124 @@ public interface {{ TypeName }}Publisher extends Publisher, {{ java.FunctionType set(value, 0); } +{% if TypeName == "Raw" %} + /** + * Publish a new value. + * + * @param value value to publish + * @param time timestamp; 0 indicates current NT time should be used + */ + default void set(byte[] value, long time) { + set(value, 0, value.length, time); + } + + /** + * Publish a new value using current NT time. + * + * @param value value to publish + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + */ + default void set(byte[] value, int start, int len) { + set(value, start, len, 0); + } + + /** + * Publish a new value. + * + * @param value value to publish + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + * @param time timestamp; 0 indicates current NT time should be used + */ + void set(byte[] value, int start, int len, long time); + + /** + * Publish a new value using current NT time. + * + * @param value value to publish; will send from value.position() to value.limit() + */ + default void set(ByteBuffer value) { + set(value, 0); + } + + /** + * Publish a new value. + * + * @param value value to publish; will send from value.position() to value.limit() + * @param time timestamp; 0 indicates current NT time should be used + */ + default void set(ByteBuffer value, long time) { + int pos = value.position(); + set(value, pos, value.limit() - pos, time); + } + + /** + * Publish a new value using current NT time. + * + * @param value value to publish + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + */ + default void set(ByteBuffer value, int start, int len) { + set(value, start, len, 0); + } + + /** + * Publish a new value. + * + * @param value value to publish + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + * @param time timestamp; 0 indicates current NT time should be used + */ + void set(ByteBuffer value, int start, int len, long time); + + /** + * Publish a default value. + * On reconnect, a default value will never be used in preference to a + * published value. + * + * @param value value + */ + default void setDefault(byte[] value) { + setDefault(value, 0, value.length); + } + + /** + * Publish a default value. + * On reconnect, a default value will never be used in preference to a + * published value. + * + * @param value value + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.length - start) + */ + void setDefault(byte[] value, int start, int len); + + /** + * Publish a default value. + * On reconnect, a default value will never be used in preference to a + * published value. + * + * @param value value; will send from value.position() to value.limit() + */ + default void setDefault(ByteBuffer value) { + int pos = value.position(); + setDefault(value, pos, value.limit() - pos); + } + + /** + * Publish a default value. + * On reconnect, a default value will never be used in preference to a + * published value. + * + * @param value value + * @param start Start position of data (in buffer) + * @param len Length of data (must be less than or equal to value.capacity() - start) + */ + void setDefault(ByteBuffer value, int start, int len); +{% else %} /** * Publish a new value. * @@ -41,7 +162,7 @@ public interface {{ TypeName }}Publisher extends Publisher, {{ java.FunctionType * @param value value */ void setDefault({{ java.ValueType }} value); - +{% endif %} @Override default void accept({{ java.ValueType }} value) { set(value); diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/RawTest.java b/ntcore/src/test/java/edu/wpi/first/networktables/RawTest.java new file mode 100644 index 0000000000..73d5efb3e7 --- /dev/null +++ b/ntcore/src/test/java/edu/wpi/first/networktables/RawTest.java @@ -0,0 +1,137 @@ +// 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.networktables; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("PMD.SimplifiableTestAssertion") +class RawTest { + private NetworkTableInstance m_inst; + + @BeforeEach + void setUp() { + m_inst = NetworkTableInstance.create(); + } + + @AfterEach + void tearDown() { + m_inst.close(); + } + + @Test + void testGenericByteArray() { + GenericEntry entry = m_inst.getTopic("test").getGenericEntry("raw"); + entry.setRaw(new byte[] {5}, 10); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {5})); + entry.setRaw(new byte[] {5, 6, 7}, 1, 2, 15); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {6, 7})); + assertThrows(IndexOutOfBoundsException.class, () -> entry.setRaw(new byte[] {5}, -1, 2, 20)); + assertThrows(IndexOutOfBoundsException.class, () -> entry.setRaw(new byte[] {5}, 1, -2, 20)); + assertThrows(IndexOutOfBoundsException.class, () -> entry.setRaw(new byte[] {5}, 1, 1, 20)); + } + + @Test + void testRawByteArray() { + RawEntry entry = m_inst.getRawTopic("test").getEntry("raw", new byte[] {}); + entry.set(new byte[] {5}, 10); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {5})); + entry.set(new byte[] {5, 6, 7}, 1, 2, 15); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {6, 7})); + assertThrows(IndexOutOfBoundsException.class, () -> entry.set(new byte[] {5}, -1, 1, 20)); + assertThrows(IndexOutOfBoundsException.class, () -> entry.set(new byte[] {5}, 1, -1, 20)); + assertThrows(IndexOutOfBoundsException.class, () -> entry.set(new byte[] {5}, 1, 1, 20)); + } + + @Test + void testGenericByteBuffer() { + GenericEntry entry = m_inst.getTopic("test").getGenericEntry("raw"); + entry.setRaw(ByteBuffer.wrap(new byte[] {5}), 10); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {5})); + entry.setRaw(ByteBuffer.wrap(new byte[] {5, 6, 7}).position(1), 15); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {6, 7})); + entry.setRaw(ByteBuffer.wrap(new byte[] {5, 6, 7}).position(1).limit(2), 16); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {6})); + entry.setRaw(ByteBuffer.wrap(new byte[] {8, 9, 0}), 1, 2, 20); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {9, 0})); + entry.setRaw(ByteBuffer.wrap(new byte[] {1, 2, 3}).position(2), 0, 2, 25); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {1, 2})); + assertThrows( + IndexOutOfBoundsException.class, + () -> entry.setRaw(ByteBuffer.wrap(new byte[] {5}), -1, 1, 30)); + assertThrows( + IndexOutOfBoundsException.class, + () -> entry.setRaw(ByteBuffer.wrap(new byte[] {5}), 1, -1, 30)); + assertThrows( + IndexOutOfBoundsException.class, + () -> entry.setRaw(ByteBuffer.wrap(new byte[] {5}), 1, 1, 30)); + } + + @Test + void testRawByteBuffer() { + RawEntry entry = m_inst.getRawTopic("test").getEntry("raw", new byte[] {}); + entry.set(ByteBuffer.wrap(new byte[] {5}), 10); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {5})); + entry.set(ByteBuffer.wrap(new byte[] {5, 6, 7}).position(1), 15); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {6, 7})); + entry.set(ByteBuffer.wrap(new byte[] {5, 6, 7}).position(1).limit(2), 16); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {6})); + entry.set(ByteBuffer.wrap(new byte[] {8, 9, 0}), 1, 2, 20); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {9, 0})); + entry.set(ByteBuffer.wrap(new byte[] {1, 2, 3}).position(2), 0, 2, 25); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {1, 2})); + assertThrows( + IndexOutOfBoundsException.class, + () -> entry.set(ByteBuffer.wrap(new byte[] {5}), -1, 1, 30)); + assertThrows( + IndexOutOfBoundsException.class, + () -> entry.set(ByteBuffer.wrap(new byte[] {5}), 1, -1, 30)); + assertThrows( + IndexOutOfBoundsException.class, + () -> entry.set(ByteBuffer.wrap(new byte[] {5}), 1, 1, 30)); + } + + @Test + void testGenericNativeByteBuffer() { + GenericEntry entry = m_inst.getTopic("test").getGenericEntry("raw"); + ByteBuffer bb = ByteBuffer.allocateDirect(3); + bb.put(new byte[] {5, 6, 7}); + entry.setRaw(bb.position(1), 15); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {6, 7})); + entry.setRaw(bb.limit(2), 16); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {6})); + bb.clear(); + bb.put(new byte[] {8, 9, 0}); + entry.setRaw(bb, 1, 2, 20); + assertTrue(Arrays.equals(entry.getRaw(new byte[] {}), new byte[] {9, 0})); + assertThrows(IndexOutOfBoundsException.class, () -> entry.setRaw(bb, -1, 1, 25)); + assertThrows(IndexOutOfBoundsException.class, () -> entry.setRaw(bb, 1, -1, 25)); + assertThrows(IndexOutOfBoundsException.class, () -> entry.setRaw(bb, 2, 2, 25)); + } + + @Test + void testRawNativeByteBuffer() { + RawEntry entry = m_inst.getRawTopic("test").getEntry("raw", new byte[] {}); + ByteBuffer bb = ByteBuffer.allocateDirect(3); + bb.put(new byte[] {5, 6, 7}); + entry.set(bb.position(1), 15); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {6, 7})); + entry.set(bb.limit(2), 16); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {6})); + bb.clear(); + bb.put(new byte[] {8, 9, 0}); + entry.set(bb, 1, 2, 20); + assertTrue(Arrays.equals(entry.get(new byte[] {}), new byte[] {9, 0})); + assertThrows(IndexOutOfBoundsException.class, () -> entry.set(bb, -1, 1, 25)); + assertThrows(IndexOutOfBoundsException.class, () -> entry.set(bb, 1, -1, 25)); + assertThrows(IndexOutOfBoundsException.class, () -> entry.set(bb, 2, 2, 25)); + } +}