Add a flatbuffer merge function
The second value wins. Vectors get appended. Nested objects get both
merged.
Change-Id: I1cb3f65b57d7cf219fca3c6d8dbb8418f8a0d697
diff --git a/aos/BUILD b/aos/BUILD
index 95418ea..46aa1b9 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -458,3 +458,27 @@
"//aos/testing:googletest",
],
)
+
+cc_library(
+ name = "flatbuffer_merge",
+ srcs = ["flatbuffer_merge.cc"],
+ hdrs = ["flatbuffer_merge.h"],
+ copts = ["-Wno-cast-align"],
+ deps = [
+ ":flatbuffer_utils",
+ "@com_github_google_flatbuffers//:flatbuffers",
+ ],
+)
+
+cc_test(
+ name = "flatbuffer_merge_test",
+ srcs = [
+ "flatbuffer_merge_test.cc",
+ ],
+ deps = [
+ ":flatbuffer_merge",
+ ":json_to_flatbuffer",
+ ":json_to_flatbuffer_flatbuffer",
+ "//aos/testing:googletest",
+ ],
+)
diff --git a/aos/flatbuffer_merge.cc b/aos/flatbuffer_merge.cc
new file mode 100644
index 0000000..3f72bb9
--- /dev/null
+++ b/aos/flatbuffer_merge.cc
@@ -0,0 +1,535 @@
+#include "aos/flatbuffer_merge.h"
+
+#include <stdio.h>
+
+#include "aos/flatbuffer_utils.h"
+#include "flatbuffers/flatbuffers.h"
+#include "flatbuffers/minireflect.h"
+
+namespace aos {
+
+namespace {
+
+// Simple structure to hold both field_offsets and elements.
+struct OffsetAndFieldOffset {
+ OffsetAndFieldOffset(flatbuffers::voffset_t new_field_offset,
+ flatbuffers::Offset<flatbuffers::String> new_element)
+ : field_offset(new_field_offset), element(new_element) {}
+
+ flatbuffers::voffset_t field_offset;
+ flatbuffers::Offset<flatbuffers::String> element;
+};
+
+// Merges 2 flat buffers with the provided type table into the builder. Returns
+// the offset to the flatbuffers.
+// One or both of t1 and t2 must be non-null. If one is null, this method
+// coppies instead of merging.
+flatbuffers::uoffset_t MergeFlatBuffers(const flatbuffers::TypeTable *typetable,
+ const flatbuffers::Table *t1,
+ const flatbuffers::Table *t2,
+ flatbuffers::FlatBufferBuilder *fbb);
+
+// Merges a single element to a builder for the provided field.
+// One or both of t1 and t2 must be non-null. If one is null, this method
+// copies instead of merging.
+template <typename T>
+void MergeElement(flatbuffers::voffset_t field_offset,
+ const flatbuffers::Table *t1, const flatbuffers::Table *t2,
+ flatbuffers::FlatBufferBuilder *fbb) {
+ const uint8_t *val1 =
+ t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+ const uint8_t *val2 =
+ t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+ const bool t1_has = val1 != nullptr;
+ const bool t2_has = val2 != nullptr;
+
+ if (t2_has) {
+ fbb->AddElement<T>(field_offset, flatbuffers::ReadScalar<T>(val2), 0);
+ } else if (t1_has) {
+ fbb->AddElement<T>(field_offset, flatbuffers::ReadScalar<T>(val1), 0);
+ }
+}
+
+// Merges a single string to a builder for the provided field.
+// One or both of t1 and t2 must be non-null. If one is null, this method
+// copies instead of merging.
+void MergeString(flatbuffers::voffset_t field_offset,
+ const flatbuffers::Table *t1, const flatbuffers::Table *t2,
+ flatbuffers::FlatBufferBuilder *fbb,
+ ::std::vector<OffsetAndFieldOffset> *elements) {
+ const uint8_t *val1 =
+ t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+ const uint8_t *val2 =
+ t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+ const bool t1_has = val1 != nullptr;
+ const bool t2_has = val2 != nullptr;
+
+ if (t2_has) {
+ val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+ const flatbuffers::String *string2 =
+ reinterpret_cast<const flatbuffers::String *>(val2);
+ elements->emplace_back(field_offset,
+ fbb->CreateString(string2->data(), string2->size()));
+ } else if (t1_has) {
+ val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+ const flatbuffers::String *string1 =
+ reinterpret_cast<const flatbuffers::String *>(val1);
+ elements->emplace_back(field_offset,
+ fbb->CreateString(string1->data(), string1->size()));
+ }
+}
+
+// Merges an object to a builder for the provided field.
+// One or both of t1 and t2 must be non-null. If one is null, this method
+// copies instead of merging.
+void MergeTables(flatbuffers::voffset_t field_offset,
+ const flatbuffers::Table *t1, const flatbuffers::Table *t2,
+ const flatbuffers::TypeTable *sub_typetable,
+ flatbuffers::FlatBufferBuilder *fbb,
+ ::std::vector<OffsetAndFieldOffset> *elements) {
+ const uint8_t *val1 =
+ t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+ const uint8_t *val2 =
+ t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+ const bool t1_has = val1 != nullptr;
+ const bool t2_has = val2 != nullptr;
+ if (t1_has || t2_has) {
+ if (val1 != nullptr) {
+ val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+ }
+ if (val2 != nullptr) {
+ val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+ }
+
+ const flatbuffers::Table *sub_t1 =
+ reinterpret_cast<const flatbuffers::Table *>(val1);
+ const flatbuffers::Table *sub_t2 =
+ reinterpret_cast<const flatbuffers::Table *>(val2);
+
+ elements->emplace_back(field_offset,
+ MergeFlatBuffers(sub_typetable, sub_t1, sub_t2, fbb));
+ }
+}
+
+// Adds a vector of strings to the elements vector so it can be added later.
+// One or both of t1 and t2 must be non-null. If one is null, this method
+// copies instead of merging.
+void AddVectorOfStrings(flatbuffers::ElementaryType elementary_type,
+ flatbuffers::voffset_t field_offset,
+ const flatbuffers::Table *t1,
+ const flatbuffers::Table *t2,
+ flatbuffers::FlatBufferBuilder *fbb,
+ ::std::vector<OffsetAndFieldOffset> *elements) {
+ const uint8_t *val1 =
+ t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+ const uint8_t *val2 =
+ t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+ const bool t1_has = val1 != nullptr;
+ const bool t2_has = val2 != nullptr;
+
+ // Compute end size of the vector.
+ size_t size = 0;
+ if (t1_has) {
+ val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+ auto vec1 = reinterpret_cast<
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *>(
+ val1);
+ size += vec1->size();
+ }
+ if (t2_has) {
+ val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+ auto vec2 = reinterpret_cast<
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *>(
+ val2);
+ size += vec2->size();
+ }
+
+ // Only add the vector if there is something to add.
+ if (t1_has || t2_has) {
+ const size_t inline_size =
+ flatbuffers::InlineSize(elementary_type, nullptr);
+
+ ::std::vector<flatbuffers::Offset<flatbuffers::String>> string_elements;
+
+ // Pack the contents in in reverse order.
+ if (t2_has) {
+ auto vec2 = reinterpret_cast<const flatbuffers::Vector<
+ flatbuffers::Offset<flatbuffers::String>> *>(val2);
+ for (auto i = vec2->rbegin(); i != vec2->rend(); ++i) {
+ const flatbuffers::String *s = *i;
+ string_elements.emplace_back(fbb->CreateString(s->data(), s->size()));
+ }
+ }
+ if (t1_has) {
+ auto vec1 = reinterpret_cast<const flatbuffers::Vector<
+ flatbuffers::Offset<flatbuffers::String>> *>(val1);
+ for (auto i = vec1->rbegin(); i != vec1->rend(); ++i) {
+ const flatbuffers::String *s = *i;
+ string_elements.emplace_back(fbb->CreateString(s->data(), s->size()));
+ }
+ }
+
+ // Start the vector.
+ fbb->StartVector(size, inline_size);
+
+ for (const flatbuffers::Offset<flatbuffers::String> &element :
+ string_elements) {
+ fbb->PushElement(element);
+ }
+
+ // And then finish the vector and put it in the list of offsets to add to
+ // the message when it finishes.
+ elements->emplace_back(
+ field_offset,
+ flatbuffers::Offset<flatbuffers::String>(fbb->EndVector(size)));
+ }
+}
+
+// Adds a vector of values to the elements vector so it can be added later.
+// One or both of t1 and t2 must be non-null. If one is null, this method
+// copies instead of merging.
+template <typename T>
+void AddVector(flatbuffers::ElementaryType elementary_type,
+ flatbuffers::voffset_t field_offset,
+ const flatbuffers::Table *t1, const flatbuffers::Table *t2,
+ flatbuffers::FlatBufferBuilder *fbb,
+ ::std::vector<OffsetAndFieldOffset> *elements) {
+ const uint8_t *val1 =
+ t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+ const uint8_t *val2 =
+ t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+ const bool t1_has = val1 != nullptr;
+ const bool t2_has = val2 != nullptr;
+
+ // Compute end size of the vector.
+ size_t size = 0;
+ if (t1_has) {
+ val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+ auto vec1 = reinterpret_cast<const flatbuffers::Vector<T> *>(val1);
+ size += vec1->size();
+ }
+ if (t2_has) {
+ val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+ auto vec2 = reinterpret_cast<const flatbuffers::Vector<T> *>(val2);
+ size += vec2->size();
+ }
+
+ // Only add the vector if there is something to add.
+ if (t1_has || t2_has) {
+ const size_t inline_size =
+ flatbuffers::InlineSize(elementary_type, nullptr);
+
+ // Start the vector.
+ fbb->StartVector(size, inline_size);
+
+ // Pack the contents in in reverse order.
+ if (t2_has) {
+ auto vec2 = reinterpret_cast<const flatbuffers::Vector<T> *>(val2);
+ // Iterate backwards.
+ for (auto i = vec2->rbegin(); i != vec2->rend(); ++i) {
+ fbb->PushElement<T>(*i);
+ }
+ }
+ if (t1_has) {
+ auto vec1 = reinterpret_cast<const flatbuffers::Vector<T> *>(val1);
+ // Iterate backwards.
+ for (auto i = vec1->rbegin(); i != vec1->rend(); ++i) {
+ fbb->PushElement<T>(*i);
+ }
+ }
+ // And then finish the vector and put it in the list of offsets to add to
+ // the message when it finishes.
+ elements->emplace_back(
+ field_offset,
+ flatbuffers::Offset<flatbuffers::String>(fbb->EndVector(size)));
+ }
+}
+
+void AddVectorOfObjects(flatbuffers::FlatBufferBuilder *fbb,
+ ::std::vector<OffsetAndFieldOffset> *elements,
+ flatbuffers::ElementaryType elementary_type,
+ const flatbuffers::TypeTable *sub_typetable,
+ flatbuffers::voffset_t field_offset,
+ const flatbuffers::Table *t1,
+ const flatbuffers::Table *t2) {
+ const uint8_t *val1 =
+ t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+ const uint8_t *val2 =
+ t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+ const bool t1_has = val1 != nullptr;
+ const bool t2_has = val2 != nullptr;
+
+ // Compute end size of the vector.
+ size_t size = 0;
+ if (t1_has) {
+ val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+ auto vec1 = reinterpret_cast<
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>> *>(
+ val1);
+ size += vec1->size();
+ }
+ if (t2_has) {
+ val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+ auto vec2 = reinterpret_cast<
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>> *>(
+ val2);
+ size += vec2->size();
+ }
+
+ // Only add the vector if there is something to add.
+ if (t1_has || t2_has) {
+ const size_t inline_size =
+ flatbuffers::InlineSize(elementary_type, sub_typetable);
+
+ ::std::vector<flatbuffers::Offset<flatbuffers::Table>> object_elements;
+
+ // Pack the contents in in reverse order.
+ if (t2_has) {
+ auto vec2 = reinterpret_cast<
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>> *>(
+ val2);
+ for (auto i = vec2->rbegin(); i != vec2->rend(); ++i) {
+ const flatbuffers::Table *t = *i;
+
+ flatbuffers::uoffset_t end =
+ MergeFlatBuffers(sub_typetable, t, nullptr, fbb);
+
+ object_elements.emplace_back(
+ flatbuffers::Offset<flatbuffers::Table>(end));
+ }
+ }
+ if (t1_has) {
+ auto vec1 = reinterpret_cast<
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>> *>(
+ val1);
+ for (auto i = vec1->rbegin(); i != vec1->rend(); ++i) {
+ const flatbuffers::Table *t = *i;
+
+ flatbuffers::uoffset_t end =
+ MergeFlatBuffers(sub_typetable, t, nullptr, fbb);
+
+ object_elements.emplace_back(
+ flatbuffers::Offset<flatbuffers::Table>(end));
+ }
+ }
+
+ // Start the vector.
+ fbb->StartVector(size, inline_size);
+
+ for (const flatbuffers::Offset<flatbuffers::Table> &element :
+ object_elements) {
+ fbb->PushElement(element);
+ }
+
+ // And then finish the vector and put it in the list of offsets to add to
+ // the message when it finishes.
+ elements->emplace_back(
+ field_offset,
+ flatbuffers::Offset<flatbuffers::String>(fbb->EndVector(size)));
+ }
+}
+
+flatbuffers::uoffset_t MergeFlatBuffers(const flatbuffers::TypeTable *typetable,
+ const flatbuffers::Table *t1,
+ const flatbuffers::Table *t2,
+ flatbuffers::FlatBufferBuilder *fbb) {
+ ::std::vector<OffsetAndFieldOffset> elements;
+
+ // We need to do this in 2 passes
+ // The first pass builds up all the sub-objects which are encoded in the
+ // message as offsets.
+ // The second pass builds up the actual table by adding all the values to the
+ // messages, and encoding the offsets in the table.
+ for (size_t field_index = 0; field_index < typetable->num_elems;
+ ++field_index) {
+ const flatbuffers::TypeCode type_code = typetable->type_codes[field_index];
+ const flatbuffers::ElementaryType elementary_type =
+ static_cast<flatbuffers::ElementaryType>(type_code.base_type);
+
+ const flatbuffers::voffset_t field_offset = flatbuffers::FieldIndexToOffset(
+ static_cast<flatbuffers::voffset_t>(field_index));
+
+ switch (elementary_type) {
+ case flatbuffers::ElementaryType::ET_UTYPE:
+ if (!type_code.is_vector) continue;
+ printf("ET_UTYPE, %s\n", typetable->names[field_index]);
+ break;
+ case flatbuffers::ElementaryType::ET_BOOL:
+ if (!type_code.is_vector) continue;
+ AddVector<uint8_t>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_CHAR:
+ if (!type_code.is_vector) continue;
+ AddVector<int8_t>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_UCHAR:
+ if (!type_code.is_vector) continue;
+ AddVector<uint8_t>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_SHORT:
+ if (!type_code.is_vector) continue;
+ AddVector<int16_t>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_USHORT:
+ if (!type_code.is_vector) continue;
+ AddVector<uint16_t>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_INT:
+ if (!type_code.is_vector) continue;
+ AddVector<int32_t>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_UINT:
+ if (!type_code.is_vector) continue;
+ AddVector<uint32_t>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_LONG:
+ if (!type_code.is_vector) continue;
+ AddVector<int64_t>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_ULONG:
+ if (!type_code.is_vector) continue;
+ AddVector<uint64_t>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_FLOAT:
+ if (!type_code.is_vector) continue;
+ AddVector<float>(elementary_type, field_offset, t1, t2, fbb, &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_DOUBLE:
+ if (!type_code.is_vector) continue;
+ AddVector<double>(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ break;
+ case flatbuffers::ElementaryType::ET_STRING:
+ if (!type_code.is_vector) {
+ MergeString(field_offset, t1, t2, fbb, &elements);
+ } else {
+ AddVectorOfStrings(elementary_type, field_offset, t1, t2, fbb,
+ &elements);
+ }
+ break;
+ case flatbuffers::ElementaryType::ET_SEQUENCE: {
+ const flatbuffers::TypeTable *sub_typetable =
+ typetable->type_refs[type_code.sequence_ref]();
+ if (!type_code.is_vector) {
+ MergeTables(field_offset, t1, t2, sub_typetable, fbb, &elements);
+ } else {
+ const flatbuffers::TypeTable *sub_typetable =
+ typetable->type_refs[type_code.sequence_ref]();
+
+ AddVectorOfObjects(fbb, &elements, elementary_type, sub_typetable,
+ field_offset, t1, t2);
+ }
+ } break;
+ }
+ }
+
+ const flatbuffers::uoffset_t start = fbb->StartTable();
+
+ // We want to do this the same way as the json library. Rip through the
+ // fields and generate a list of things to add. Then add them.
+ // Also need recursion for subtypes.
+ for (size_t field_index = 0; field_index < typetable->num_elems;
+ ++field_index) {
+ const flatbuffers::TypeCode type_code = typetable->type_codes[field_index];
+ if (type_code.is_vector) {
+ continue;
+ }
+ const flatbuffers::ElementaryType elementary_type =
+ static_cast<flatbuffers::ElementaryType>(type_code.base_type);
+
+ const flatbuffers::voffset_t field_offset = flatbuffers::FieldIndexToOffset(
+ static_cast<flatbuffers::voffset_t>(field_index));
+
+ switch (elementary_type) {
+ case flatbuffers::ElementaryType::ET_UTYPE:
+ // TODO(austin): Need to see one and try it.
+ printf("ET_UTYPE, %s\n", typetable->names[field_index]);
+ break;
+ case flatbuffers::ElementaryType::ET_BOOL: {
+ MergeElement<uint8_t>(field_offset, t1, t2, fbb);
+ } break;
+ case flatbuffers::ElementaryType::ET_CHAR:
+ MergeElement<int8_t>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_UCHAR:
+ MergeElement<uint8_t>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_SHORT:
+ MergeElement<int16_t>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_USHORT:
+ MergeElement<uint16_t>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_INT:
+ MergeElement<int32_t>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_UINT:
+ MergeElement<uint32_t>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_LONG:
+ MergeElement<int64_t>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_ULONG:
+ MergeElement<uint64_t>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_FLOAT:
+ MergeElement<float>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_DOUBLE:
+ MergeElement<double>(field_offset, t1, t2, fbb);
+ break;
+ case flatbuffers::ElementaryType::ET_STRING:
+ case flatbuffers::ElementaryType::ET_SEQUENCE:
+ // Already handled above since this is an uoffset.
+ break;
+ }
+ }
+
+ // And there is no need to check for duplicates since we are creating this
+ // list very carefully from the type table.
+ for (const OffsetAndFieldOffset &element : elements) {
+ fbb->AddOffset(element.field_offset, element.element);
+ }
+
+ return fbb->EndTable(start);
+}
+
+} // namespace
+
+::std::vector<uint8_t> MergeFlatBuffers(const flatbuffers::TypeTable *typetable,
+ const uint8_t *data1,
+ const uint8_t *data2) {
+ // Build up a builder.
+ flatbuffers::FlatBufferBuilder fbb;
+ fbb.ForceDefaults(1);
+
+ // Grab the 2 tables.
+ const flatbuffers::Table *t1 =
+ data1 != nullptr ? flatbuffers::GetRoot<flatbuffers::Table>(data1)
+ : nullptr;
+ const flatbuffers::Table *t2 =
+ data2 != nullptr ? flatbuffers::GetRoot<flatbuffers::Table>(data2)
+ : nullptr;
+
+ // And then do the actual build. This doesn't contain finish so we can nest
+ // them nicely.
+ flatbuffers::uoffset_t end = MergeFlatBuffers(typetable, t1, t2, &fbb);
+
+ // Finish up the buffer and return it.
+ fbb.Finish(flatbuffers::Offset<flatbuffers::Table>(end));
+
+ const uint8_t *buf = fbb.GetBufferPointer();
+ const int size = fbb.GetSize();
+ return ::std::vector<uint8_t>(buf, buf + size);
+}
+
+} // namespace aos
diff --git a/aos/flatbuffer_merge.h b/aos/flatbuffer_merge.h
new file mode 100644
index 0000000..c63b143
--- /dev/null
+++ b/aos/flatbuffer_merge.h
@@ -0,0 +1,23 @@
+#ifndef AOS_FLATBUFFER_MERGE_H_
+#define AOS_FLATBUFFER_MERGE_H_
+
+#include <cstddef>
+#include <string>
+
+#include "flatbuffers/flatbuffers.h"
+
+namespace aos {
+
+::std::vector<uint8_t> MergeFlatBuffers(const flatbuffers::TypeTable *typetable,
+ const uint8_t *data1,
+ const uint8_t *data2);
+
+template <class T>
+::std::vector<uint8_t> MergeFlatBuffers(const uint8_t *data1,
+ const uint8_t *data2) {
+ return MergeFlatBuffers(T::MiniReflectTypeTable(), data1, data2);
+}
+
+} // namespace aos
+
+#endif // AOS_FLATBUFFER_MERGE_H_
diff --git a/aos/flatbuffer_merge_test.cc b/aos/flatbuffer_merge_test.cc
new file mode 100644
index 0000000..f102db9
--- /dev/null
+++ b/aos/flatbuffer_merge_test.cc
@@ -0,0 +1,310 @@
+#include "aos/flatbuffer_merge.h"
+
+#include "gtest/gtest.h"
+
+#include "aos/json_to_flatbuffer.h"
+#include "aos/json_to_flatbuffer_generated.h"
+#include "flatbuffers/minireflect.h"
+
+namespace aos {
+namespace testing {
+
+class FlatbufferMerge : public ::testing::Test {
+ public:
+ FlatbufferMerge() {}
+
+ void JsonMerge(const ::std::string in1, const ::std::string in2,
+ const ::std::string out) {
+ printf("Merging: %s\n", in1.c_str());
+ printf("Merging: %s\n", in2.c_str());
+ const ::std::vector<uint8_t> fb1 = JsonToFlatbuffer(
+ static_cast<const char *>(in1.c_str()), ConfigurationTypeTable());
+
+ const ::std::string in1_nested = "{ \"nested_config\": " + in1 + " }";
+ const ::std::vector<uint8_t> fb1_nested = JsonToFlatbuffer(
+ static_cast<const char *>(in1_nested.c_str()),
+ ConfigurationTypeTable());
+
+ const ::std::vector<uint8_t> fb2 = JsonToFlatbuffer(
+ static_cast<const char *>(in2.c_str()), ConfigurationTypeTable());
+
+ const ::std::string in2_nested = "{ \"nested_config\": " + in2 + " }";
+ const ::std::vector<uint8_t> fb2_nested =
+ JsonToFlatbuffer(static_cast<const char *>(in2_nested.c_str()),
+ ConfigurationTypeTable());
+
+ const ::std::string out_nested = "{ \"nested_config\": " + out + " }";
+
+ const ::std::vector<uint8_t> empty =
+ JsonToFlatbuffer("{ }", ConfigurationTypeTable());
+
+ ASSERT_NE(fb1.size(), 0u);
+ ASSERT_NE(fb2.size(), 0u);
+ ASSERT_NE(fb1_nested.size(), 0u);
+ ASSERT_NE(fb2_nested.size(), 0u);
+
+ // We now want to run 7 tests.
+ // in1 merged "" -> in1.
+ // in2 merged "" -> in2.
+ // "" merged in1 -> in1.
+ // "" merged in2 -> in2.
+ // nullptr merged in1 -> in1.
+ // in1 merged nullptr -> in1.
+ // in1 merged in2 -> out.
+
+ {
+ // in1 merged with "" => in1.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(fb1.data(), empty.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in1,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // in2 merged with "" => in2.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(fb2.data(), empty.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in2,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // "" merged with in1 => in1.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(empty.data(), fb1.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in1,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // "" merged with in2 => in2.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(empty.data(), fb2.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in2,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // nullptr merged with in1 => in1.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(nullptr, fb1.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in1, FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // in1 merged with nullptr => in1.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(fb1.data(), nullptr);
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in1, FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // in1 merged with in2 => out.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(fb1.data(), fb2.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ const ::std::string merged_output =
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable());
+ EXPECT_EQ(out, merged_output);
+
+ printf("Merged to: %s\n", merged_output.c_str());
+ }
+
+ // Now, to make things extra exciting, nest a config inside a config. And
+ // run all the tests. This will exercise some fun nested merges and copies.
+
+ {
+ // in1_nested merged with "" => in1.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(fb1_nested.data(), empty.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in1_nested,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // in2_nested merged with "" => in2_nested.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(fb2_nested.data(), empty.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in2_nested,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // "" merged with in1_nested => in1_nested.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(empty.data(), fb1_nested.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in1_nested,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // "" merged with in2_nested => in2_nested.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(empty.data(), fb2_nested.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in2_nested,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // nullptr merged with in1_nested => in1_nested.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(nullptr, fb1_nested.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in1_nested,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // nullptr merged with in1_nested => in1_nested.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(fb1_nested.data(), nullptr);
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(in1_nested,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+
+ {
+ // in1_nested merged with in2_nested => out_nested.
+ ::std::vector<uint8_t> fb_merged =
+ MergeFlatBuffers<Configuration>(fb1_nested.data(), fb2_nested.data());
+ ASSERT_NE(fb_merged.size(), 0u);
+
+ EXPECT_EQ(out_nested,
+ FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+ }
+ }
+};
+
+// Test the easy ones. Test every type, single, no nesting.
+TEST_F(FlatbufferMerge, Basic) {
+ JsonMerge("{ \"foo_bool\": true }", "{ \"foo_bool\": false }",
+ "{ \"foo_bool\": false }");
+
+ JsonMerge("{ \"foo_bool\": false }", "{ \"foo_bool\": true }",
+ "{ \"foo_bool\": true }");
+
+ JsonMerge("{ \"foo_byte\": 5 }", "{ \"foo_byte\": -7 }",
+ "{ \"foo_byte\": -7 }");
+
+ JsonMerge("{ \"foo_ubyte\": 5 }", "{ \"foo_ubyte\": 7 }",
+ "{ \"foo_ubyte\": 7 }");
+
+ JsonMerge("{ \"foo_short\": 5 }", "{ \"foo_short\": 7 }",
+ "{ \"foo_short\": 7 }");
+
+ JsonMerge("{ \"foo_int\": 5 }", "{ \"foo_int\": 7 }", "{ \"foo_int\": 7 }");
+
+ JsonMerge("{ \"foo_uint\": 5 }", "{ \"foo_uint\": 7 }",
+ "{ \"foo_uint\": 7 }");
+
+ JsonMerge("{ \"foo_long\": 5 }", "{ \"foo_long\": 7 }",
+ "{ \"foo_long\": 7 }");
+
+ JsonMerge("{ \"foo_ulong\": 5 }", "{ \"foo_ulong\": 7 }",
+ "{ \"foo_ulong\": 7 }");
+
+ JsonMerge("{ \"foo_float\": 5.0 }", "{ \"foo_float\": 7.1 }",
+ "{ \"foo_float\": 7.1 }");
+
+ JsonMerge("{ \"foo_double\": 5.0 }", "{ \"foo_double\": 7.1 }",
+ "{ \"foo_double\": 7.1 }");
+}
+
+// Test arrays of simple types.
+TEST_F(FlatbufferMerge, Array) {
+ JsonMerge("{ \"foo_string\": \"baz\" }", "{ \"foo_string\": \"bar\" }",
+ "{ \"foo_string\": \"bar\" }");
+
+ JsonMerge("{ \"vector_foo_bool\": [ true, true, false ] }",
+ "{ \"vector_foo_bool\": [ false, true, true, false ] }",
+ "{ \"vector_foo_bool\": [ true, true, false, false, "
+ "true, true, false ] }");
+
+ JsonMerge("{ \"vector_foo_byte\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_byte\": [ -3, 1, 3, 2 ] }",
+ "{ \"vector_foo_byte\": [ 9, 7, 1, -3, 1, 3, 2 ] }");
+
+ JsonMerge("{ \"vector_foo_ubyte\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_ubyte\": [ 3, 1, 3, 2 ] }",
+ "{ \"vector_foo_ubyte\": [ 9, 7, 1, 3, 1, 3, 2 ] }");
+
+ JsonMerge("{ \"vector_foo_short\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_short\": [ -3, 1, 3, 2 ] }",
+ "{ \"vector_foo_short\": [ 9, 7, 1, -3, 1, 3, 2 ] }");
+
+ JsonMerge("{ \"vector_foo_ushort\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_ushort\": [ 3, 1, 3, 2 ] }",
+ "{ \"vector_foo_ushort\": [ 9, 7, 1, 3, 1, 3, 2 ] }");
+
+ JsonMerge("{ \"vector_foo_int\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_int\": [ -3, 1, 3, 2 ] }",
+ "{ \"vector_foo_int\": [ 9, 7, 1, -3, 1, 3, 2 ] }");
+
+ JsonMerge("{ \"vector_foo_uint\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_uint\": [ 3, 1, 3, 2 ] }",
+ "{ \"vector_foo_uint\": [ 9, 7, 1, 3, 1, 3, 2 ] }");
+
+ JsonMerge("{ \"vector_foo_long\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_long\": [ -3, 1, 3, 2 ] }",
+ "{ \"vector_foo_long\": [ 9, 7, 1, -3, 1, 3, 2 ] }");
+
+ JsonMerge("{ \"vector_foo_ulong\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_ulong\": [ 3, 1, 3, 2 ] }",
+ "{ \"vector_foo_ulong\": [ 9, 7, 1, 3, 1, 3, 2 ] }");
+
+ JsonMerge("{ \"vector_foo_float\": [ 9.0, 7.0, 1.0 ] }",
+ "{ \"vector_foo_float\": [ -3.0, 1.3, 3.0, 2.0 ] }",
+ "{ \"vector_foo_float\": [ 9.0, 7.0, 1.0, -3.0, 1.3, 3.0, 2.0 ] }");
+
+ JsonMerge("{ \"vector_foo_string\": [ \"9\", \"7\", \"1 \" ] }",
+ "{ \"vector_foo_string\": [ \"31\", \"32\" ] }",
+ "{ \"vector_foo_string\": [ \"9\", \"7\", \"1 \", \"31\", \"32\" ] }");
+}
+
+// Test nested messages, and arrays of nested messages.
+TEST_F(FlatbufferMerge, NestedStruct) {
+ JsonMerge(
+ "{ \"single_application\": { \"name\": \"woot\" } }",
+ "{ \"single_application\": { \"name\": \"wow\", \"priority\": 7 } }",
+ "{ \"single_application\": { \"name\": \"wow\", \"priority\": 7 } }");
+
+ JsonMerge(
+ "{ \"single_application\": { \"name\": \"wow\", \"priority\": 7 } }",
+ "{ \"single_application\": { \"name\": \"woot\" } }",
+ "{ \"single_application\": { \"name\": \"woot\", \"priority\": 7 } }");
+
+ JsonMerge("{ \"apps\": [ { \"name\": \"woot\" }, { \"name\": \"wow\" } ] }",
+ "{ \"apps\": [ { \"name\": \"woo2\" }, { \"name\": \"wo3\" } ] }",
+ "{ \"apps\": [ { \"name\": \"woot\" }, { \"name\": \"wow\" }, { "
+ "\"name\": \"woo2\" }, { \"name\": \"wo3\" } ] }");
+}
+
+// TODO(austin): enums
+// TODO(austin): unions
+// TODO(austin): struct
+
+} // namespace testing
+} // namespace aos
diff --git a/aos/json_to_flatbuffer.fbs b/aos/json_to_flatbuffer.fbs
index 9743345..2e90a53 100644
--- a/aos/json_to_flatbuffer.fbs
+++ b/aos/json_to_flatbuffer.fbs
@@ -14,13 +14,14 @@
table Application {
name:string;
+ priority:int;
maps:[Map];
}
table Configuration {
locations:[Location] (id: 0);
maps:[Map] (id: 1);
- applications:[Application] (id: 2);
+ apps:[Application] (id: 2);
imports:[string] (id: 3);
// 8 bit: byte ubyte bool
@@ -68,6 +69,11 @@
// And a simple nested application.
single_application:Application (id: 28);
+
+ // TODO(austin): enum
+
+ // And nest this object to get some crazy coverage.
+ nested_config:Configuration (id: 29);
}
root_type Configuration;
diff --git a/aos/json_to_flatbuffer_test.cc b/aos/json_to_flatbuffer_test.cc
index 4b0629e..82bbd52 100644
--- a/aos/json_to_flatbuffer_test.cc
+++ b/aos/json_to_flatbuffer_test.cc
@@ -67,6 +67,7 @@
EXPECT_TRUE(JsonAndBack("{ \"foo_double\": 5.1 }"));
}
+
// Test what happens if you pass a field name that we don't know.
TEST_F(JsonToFlatbufferTest, InvalidFieldName) {
EXPECT_FALSE(JsonAndBack("{ \"foo\": 5 }"));
@@ -89,13 +90,12 @@
EXPECT_FALSE(JsonAndBack("{ \"foo_int\": 5, }", "{ \"foo_int\": 5 }"));
- EXPECT_FALSE(JsonAndBack(
- "{ \"applications\":\n[\n{\n\"name\": \"woot\"\n},\n{\n\"name\": "
- "\"wow\"\n} ,\n]\n}"));
-
EXPECT_FALSE(
- JsonAndBack("{ \"applications\": [ { \"name\": \"woot\" }, { \"name\": "
- "\"wow\" } ] , }"));
+ JsonAndBack("{ \"apps\":\n[\n{\n\"name\": \"woot\"\n},\n{\n\"name\": "
+ "\"wow\"\n} ,\n]\n}"));
+
+ EXPECT_FALSE(JsonAndBack(
+ "{ \"apps\": [ { \"name\": \"woot\" }, { \"name\": \"wow\" } ] , }"));
EXPECT_FALSE(
JsonAndBack("{ \"vector_foo_string\": [ \"bar\", \"baz\" ] , }"));
@@ -134,17 +134,21 @@
EXPECT_TRUE(
JsonAndBack("{ \"single_application\": { \"name\": \"woot\" } }"));
- EXPECT_TRUE(
- JsonAndBack("{ \"applications\": [ { \"name\": \"woot\" }, { \"name\": "
- "\"wow\" } ] }"));
+ EXPECT_TRUE(JsonAndBack(
+ "{ \"apps\": [ { \"name\": \"woot\" }, { \"name\": \"wow\" } ] }"));
}
// Test that we can parse an empty message.
TEST_F(JsonToFlatbufferTest, EmptyMessage) {
+ // Empty message works.
EXPECT_TRUE(JsonAndBack("{ }"));
}
+
// TODO(austin): Missmatched values.
+// TODO(austin): enums
+//
+// TODO(austin): unions?
} // namespace testing
} // namespace aos