diff --git a/src/main/java/edu/wpi/first/networktables/NetworkTable.java b/src/main/java/edu/wpi/first/networktables/NetworkTable.java
index 2ff3c2917b..226b7c3a23 100644
--- a/src/main/java/edu/wpi/first/networktables/NetworkTable.java
+++ b/src/main/java/edu/wpi/first/networktables/NetworkTable.java
@@ -7,7 +7,9 @@
package edu.wpi.first.networktables;
+import java.util.ArrayList;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -28,6 +30,93 @@ public final class NetworkTable {
private final String pathWithSep;
private final NetworkTableInstance inst;
+ /**
+ * Gets the "base name" of a key. For example, "/foo/bar" becomes "bar".
+ * If the key has a trailing slash, returns an empty string.
+ * @param key key
+ * @return base name
+ */
+ public static String basenameKey(String key) {
+ final int slash = key.lastIndexOf(PATH_SEPARATOR);
+ if (slash == -1) {
+ return key;
+ }
+ return key.substring(slash + 1);
+ }
+
+ /**
+ * Normalizes an network table key to contain no consecutive slashes and
+ * optionally start with a leading slash. For example:
+ *
+ *
+ * normalizeKey("/foo/bar", true) == "/foo/bar"
+ * normalizeKey("foo/bar", true) == "/foo/bar"
+ * normalizeKey("/foo/bar", false) == "foo/bar"
+ * normalizeKey("foo//bar", false) == "foo/bar"
+ *
+ *
+ * @param key the key to normalize
+ * @param withLeadingSlash whether or not the normalized key should begin
+ * with a leading slash
+ * @return normalized key
+ */
+ public static String normalizeKey(String key, boolean withLeadingSlash) {
+ String normalized;
+ if (withLeadingSlash) {
+ normalized = PATH_SEPARATOR + key;
+ } else {
+ normalized = key;
+ }
+ normalized = normalized.replaceAll(PATH_SEPARATOR + "{2,}", String.valueOf(PATH_SEPARATOR));
+
+ if (!withLeadingSlash && normalized.charAt(0) == PATH_SEPARATOR) {
+ // remove leading slash, if present
+ normalized = normalized.substring(1);
+ }
+ return normalized;
+ }
+
+ /**
+ * Normalizes a network table key to start with exactly one leading slash
+ * ("/") and contain no consecutive slashes. For example,
+ * {@code "//foo/bar/"} becomes {@code "/foo/bar/"} and
+ * {@code "///a/b/c"} becomes {@code "/a/b/c"}.
+ *
+ * This is equivalent to {@code normalizeKey(key, true)}
+ *
+ * @param key the key to normalize
+ * @return normalized key
+ */
+ public static String normalizeKey(String key) {
+ return normalizeKey(key, true);
+ }
+
+ /**
+ * Gets a list of the names of all the super tables of a given key. For
+ * example, the key "/foo/bar/baz" has a hierarchy of "/", "/foo",
+ * "/foo/bar", and "/foo/bar/baz".
+ * @param key the key
+ * @return List of super tables
+ */
+ public static List getHierarchy(String key) {
+ final String normal = normalizeKey(key, true);
+ List hierarchy = new ArrayList<>();
+ if (normal.length() == 1) {
+ hierarchy.add(normal);
+ return hierarchy;
+ }
+ for (int i = 1; ; i = normal.indexOf(PATH_SEPARATOR, i + 1)) {
+ if (i == -1) {
+ // add the full key
+ hierarchy.add(normal);
+ break;
+ } else {
+ hierarchy.add(normal.substring(0, i));
+ }
+ }
+ return hierarchy;
+ }
+
/**
* Constructor. Use NetworkTableInstance.getTable() or getSubTable() instead.
*/
diff --git a/src/main/native/cpp/networktables/NetworkTable.cpp b/src/main/native/cpp/networktables/NetworkTable.cpp
index 07361bba9d..138cfcea76 100644
--- a/src/main/native/cpp/networktables/NetworkTable.cpp
+++ b/src/main/native/cpp/networktables/NetworkTable.cpp
@@ -22,6 +22,56 @@ bool NetworkTable::s_enable_ds = true;
bool NetworkTable::s_running = false;
unsigned int NetworkTable::s_port = NT_DEFAULT_PORT;
+StringRef NetworkTable::BasenameKey(StringRef key) {
+ size_t slash = key.rfind(PATH_SEPARATOR_CHAR);
+ if (slash == StringRef::npos) return key;
+ return key.substr(slash + 1);
+}
+
+std::string NetworkTable::NormalizeKey(StringRef key, bool withLeadingSlash) {
+ llvm::SmallString<128> buf;
+ return NormalizeKey(key, buf, withLeadingSlash);
+}
+
+StringRef NetworkTable::NormalizeKey(StringRef key,
+ llvm::SmallVectorImpl& buf,
+ bool withLeadingSlash) {
+ buf.clear();
+ if (withLeadingSlash) buf.push_back(PATH_SEPARATOR_CHAR);
+ // for each path element, add it with a slash following
+ llvm::SmallVector parts;
+ key.split(parts, PATH_SEPARATOR_CHAR, -1, false);
+ for (auto i = parts.begin(); i != parts.end(); ++i) {
+ buf.append(i->begin(), i->end());
+ buf.push_back(PATH_SEPARATOR_CHAR);
+ }
+ // remove trailing slash if the input key didn't have one
+ if (!key.empty() && key.back() != PATH_SEPARATOR_CHAR) buf.pop_back();
+ return StringRef(buf.data(), buf.size());
+}
+
+std::vector NetworkTable::GetHierarchy(StringRef key) {
+ std::vector hierarchy;
+ hierarchy.emplace_back(1, PATH_SEPARATOR_CHAR);
+ // for each path element, add it to the end of what we built previously
+ llvm::SmallString<128> path;
+ llvm::SmallVector parts;
+ key.split(parts, PATH_SEPARATOR_CHAR, -1, false);
+ if (!parts.empty()) {
+ for (auto i = parts.begin(); i != parts.end(); ++i) {
+ path += PATH_SEPARATOR_CHAR;
+ path += *i;
+ hierarchy.emplace_back(path.str());
+ }
+ // handle trailing slash
+ if (key.back() == PATH_SEPARATOR_CHAR) {
+ path += PATH_SEPARATOR_CHAR;
+ hierarchy.emplace_back(path.str());
+ }
+ }
+ return hierarchy;
+}
+
void NetworkTable::Initialize() {
if (s_running) Shutdown();
auto inst = NetworkTableInstance::GetDefault();
diff --git a/src/main/native/include/networktables/NetworkTable.h b/src/main/native/include/networktables/NetworkTable.h
index aa563d0de6..2e23a2154d 100644
--- a/src/main/native/include/networktables/NetworkTable.h
+++ b/src/main/native/include/networktables/NetworkTable.h
@@ -11,6 +11,7 @@
#include
#include
+#include "llvm/ArrayRef.h"
#include "llvm/StringMap.h"
#include "networktables/NetworkTableEntry.h"
#include "networktables/TableEntryListener.h"
@@ -54,6 +55,44 @@ class NetworkTable final : public ITable {
friend class NetworkTableInstance;
public:
+ /**
+ * Gets the "base name" of a key. For example, "/foo/bar" becomes "bar".
+ * If the key has a trailing slash, returns an empty string.
+ * @param key key
+ * @return base name
+ */
+ static StringRef BasenameKey(StringRef key);
+
+ /**
+ * Normalizes an network table key to contain no consecutive slashes and
+ * optionally start with a leading slash. For example:
+ *
+ *
+ * normalizeKey("/foo/bar", true) == "/foo/bar"
+ * normalizeKey("foo/bar", true) == "/foo/bar"
+ * normalizeKey("/foo/bar", false) == "foo/bar"
+ * normalizeKey("foo//bar", false) == "foo/bar"
+ *
+ *
+ * @param key the key to normalize
+ * @param withLeadingSlash whether or not the normalized key should begin
+ * with a leading slash
+ * @return normalized key
+ */
+ static std::string NormalizeKey(StringRef key, bool withLeadingSlash = true);
+
+ static StringRef NormalizeKey(StringRef key, llvm::SmallVectorImpl& buf,
+ bool withLeadingSlash = true);
+
+ /**
+ * Gets a list of the names of all the super tables of a given key. For
+ * example, the key "/foo/bar/baz" has a hierarchy of "/", "/foo",
+ * "/foo/bar", and "/foo/bar/baz".
+ * @param key the key
+ * @return List of super tables
+ */
+ static std::vector GetHierarchy(StringRef key);
+
/**
* Constructor. Use NetworkTableInstance::GetTable() or GetSubTable()
* instead.
diff --git a/src/test/java/edu/wpi/first/networktables/NetworkTableTest.java b/src/test/java/edu/wpi/first/networktables/NetworkTableTest.java
new file mode 100644
index 0000000000..d8b2967d67
--- /dev/null
+++ b/src/test/java/edu/wpi/first/networktables/NetworkTableTest.java
@@ -0,0 +1,67 @@
+package edu.wpi.first.networktables;
+
+import java.util.ArrayList;
+import java.util.List;
+import junit.framework.TestCase;
+
+public class NetworkTableTest extends TestCase {
+
+ public void testBasenameKey() {
+ assertEquals("simple", NetworkTable.basenameKey("simple"));
+ assertEquals("simple", NetworkTable.basenameKey("one/two/many/simple"));
+ assertEquals("simple",
+ NetworkTable.basenameKey("//////an/////awful/key////simple"));
+ }
+
+ public void testNormalizeKeySlash() {
+ assertEquals("/", NetworkTable.normalizeKey("///"));
+ assertEquals("/no/normal/req", NetworkTable.normalizeKey("/no/normal/req"));
+ assertEquals("/no/leading/slash",
+ NetworkTable.normalizeKey("no/leading/slash"));
+ assertEquals(
+ "/what/an/awful/key/",
+ NetworkTable.normalizeKey("//////what////an/awful/////key///"));
+ }
+
+ public void testNormalizeKeyNoSlash() {
+ assertEquals("a", NetworkTable.normalizeKey("a", false));
+ assertEquals("a", NetworkTable.normalizeKey("///a", false));
+ assertEquals("leading/slash",
+ NetworkTable.normalizeKey("/leading/slash", false));
+ assertEquals("no/leading/slash",
+ NetworkTable.normalizeKey("no/leading/slash", false));
+ assertEquals(
+ "what/an/awful/key/",
+ NetworkTable.normalizeKey("//////what////an/awful/////key///", false));
+ }
+
+ public void testGetHierarchyEmpty() {
+ List expected = new ArrayList<>();
+ expected.add("/");
+ assertEquals(expected, NetworkTable.getHierarchy(""));
+ }
+
+ public void testGetHierarchyRoot() {
+ List expected = new ArrayList<>();
+ expected.add("/");
+ assertEquals(expected, NetworkTable.getHierarchy("/"));
+ }
+
+ public void testGetHierarchyNormal() {
+ List expected = new ArrayList<>();
+ expected.add("/");
+ expected.add("/foo");
+ expected.add("/foo/bar");
+ expected.add("/foo/bar/baz");
+ assertEquals(expected, NetworkTable.getHierarchy("/foo/bar/baz"));
+ }
+
+ public void testGetHierarchyTrailingSlash() {
+ List expected = new ArrayList<>();
+ expected.add("/");
+ expected.add("/foo");
+ expected.add("/foo/bar");
+ expected.add("/foo/bar/");
+ assertEquals(expected, NetworkTable.getHierarchy("/foo/bar/"));
+ }
+}
diff --git a/src/test/native/cpp/NetworkTableTest.cpp b/src/test/native/cpp/NetworkTableTest.cpp
index 706f059934..044eaf19cc 100644
--- a/src/test/native/cpp/NetworkTableTest.cpp
+++ b/src/test/native/cpp/NetworkTableTest.cpp
@@ -13,6 +13,54 @@
class NetworkTableTest : public ::testing::Test {};
+TEST_F(NetworkTableTest, BasenameKey) {
+ EXPECT_EQ("simple", NetworkTable::BasenameKey("simple"));
+ EXPECT_EQ("simple", NetworkTable::BasenameKey("one/two/many/simple"));
+ EXPECT_EQ("simple",
+ NetworkTable::BasenameKey("//////an/////awful/key////simple"));
+}
+
+TEST_F(NetworkTableTest, NormalizeKeySlash) {
+ EXPECT_EQ("/", NetworkTable::NormalizeKey("///"));
+ EXPECT_EQ("/no/normal/req", NetworkTable::NormalizeKey("/no/normal/req"));
+ EXPECT_EQ("/no/leading/slash",
+ NetworkTable::NormalizeKey("no/leading/slash"));
+ EXPECT_EQ("/what/an/awful/key/",
+ NetworkTable::NormalizeKey("//////what////an/awful/////key///"));
+}
+
+TEST_F(NetworkTableTest, NormalizeKeyNoSlash) {
+ EXPECT_EQ("a", NetworkTable::NormalizeKey("a", false));
+ EXPECT_EQ("a", NetworkTable::NormalizeKey("///a", false));
+ EXPECT_EQ("leading/slash",
+ NetworkTable::NormalizeKey("/leading/slash", false));
+ EXPECT_EQ("no/leading/slash",
+ NetworkTable::NormalizeKey("no/leading/slash", false));
+ EXPECT_EQ(
+ "what/an/awful/key/",
+ NetworkTable::NormalizeKey("//////what////an/awful/////key///", false));
+}
+
+TEST_F(NetworkTableTest, GetHierarchyEmpty) {
+ std::vector expected{"/"};
+ ASSERT_EQ(expected, NetworkTable::GetHierarchy(""));
+}
+
+TEST_F(NetworkTableTest, GetHierarchyRoot) {
+ std::vector expected{"/"};
+ ASSERT_EQ(expected, NetworkTable::GetHierarchy("/"));
+}
+
+TEST_F(NetworkTableTest, GetHierarchyNormal) {
+ std::vector expected{"/", "/foo", "/foo/bar", "/foo/bar/baz"};
+ ASSERT_EQ(expected, NetworkTable::GetHierarchy("/foo/bar/baz"));
+}
+
+TEST_F(NetworkTableTest, GetHierarchyTrailingSlash) {
+ std::vector expected{"/", "/foo", "/foo/bar", "/foo/bar/"};
+ ASSERT_EQ(expected, NetworkTable::GetHierarchy("/foo/bar/"));
+}
+
TEST_F(NetworkTableTest, ContainsKey) {
auto inst = nt::NetworkTableInstance::Create();
auto nt = inst.GetTable("containskey");