From bcc0f65926033f5dd410b5b57348c78739b47c2a Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Sat, 14 Aug 2021 16:16:54 +0200
Subject: [PATCH] WIP: consolidation

---
 include/caosdb/data_type.h       | 116 +++++++++++++++++++++++++++----
 include/caosdb/entity.h          |  15 ++--
 include/caosdb/protobuf_helper.h |   6 +-
 include/caosdb/value.h           |   7 +-
 proto                            |   2 +-
 src/caosdb/entity.cpp            |  11 ++-
 test/CMakeLists.txt              |   1 +
 test/test_data_type.cpp          |  69 ++++++++++++++++++
 test/test_entity.cpp             |  41 ++++++++---
 9 files changed, 235 insertions(+), 33 deletions(-)
 create mode 100644 test/test_data_type.cpp

diff --git a/include/caosdb/data_type.h b/include/caosdb/data_type.h
index 194a2a6..c21fe0d 100644
--- a/include/caosdb/data_type.h
+++ b/include/caosdb/data_type.h
@@ -27,10 +27,16 @@
 namespace caosdb::entity {
 using ProtoAtomicDataType = caosdb::entity::v1alpha1::AtomicDataType;
 using ProtoDataType = caosdb::entity::v1alpha1::DataType;
+using ProtoListDataType = caosdb::entity::v1alpha1::ListDataType;
 using ProtoReferenceDataType = caosdb::entity::v1alpha1::ReferenceDataType;
 using DataTypeCase = caosdb::entity::v1alpha1::DataType::DataTypeCase;
+using ListDataTypeCase =
+  caosdb::entity::v1alpha1::ListDataType::ListDataTypeCase;
 using caosdb::utility::ProtoMessageWrapper;
 
+class Entity;
+class Property;
+
 // Atomic data types.
 enum AtomicDataType {
   // The data type is unset/unknown.
@@ -48,52 +54,136 @@ enum AtomicDataType {
 };
 
 class DataType;
+class ListDataType;
 
-class ReferenceDataType : ProtoMessageWrapper<ProtoReferenceDataType> {
+class ReferenceDataType : public ProtoMessageWrapper<ProtoDataType> {
 public:
   [[nodiscard]] inline auto GetName() const noexcept -> const std::string & {
-    return this->wrapped->name();
+    return this->wrapped->reference_data_type().name();
+  }
+
+  friend class DataType;
+  friend class ListDataType;
+
+protected:
+  static auto GetEmptyInstance() -> const ReferenceDataType & {
+    static ReferenceDataType instance;
+    return instance;
+  }
+  inline static auto Create(ProtoDataType *wrapped)
+    -> std::unique_ptr<ReferenceDataType> {
+    return std::unique_ptr<ReferenceDataType>(new ReferenceDataType(wrapped));
+  }
+  ReferenceDataType() : ProtoMessageWrapper<ProtoDataType>() {}
+  ReferenceDataType(ProtoDataType *wrapped)
+    : ProtoMessageWrapper<ProtoDataType>(wrapped) {}
+};
+
+class ListDataType : public ProtoMessageWrapper<ProtoDataType> {
+public:
+  [[nodiscard]] inline auto IsListOfReference() const noexcept -> bool {
+    return this->wrapped->list_data_type().list_data_type_case() ==
+           ListDataTypeCase::kReferenceDataType;
+  }
+  [[nodiscard]] inline auto GetReferenceDataType() const
+    -> const ReferenceDataType & {
+    if (!IsListOfReference()) {
+      return ReferenceDataType::GetEmptyInstance();
+    }
+    if (reference_data_type == nullptr) {
+      this->reference_data_type = std::unique_ptr<ReferenceDataType>(
+        ReferenceDataType::Create(this->wrapped).release());
+    }
+    return *this->reference_data_type;
   }
 
-  ReferenceDataType() : ProtoMessageWrapper<ProtoReferenceDataType>() {}
+  [[nodiscard]] inline auto IsListOfAtomic() const noexcept -> bool {
+    return this->wrapped->list_data_type().list_data_type_case() ==
+           ListDataTypeCase::kAtomicDataType;
+  }
+  [[nodiscard]] inline auto GetAtomicDataType() const -> AtomicDataType {
+    return static_cast<AtomicDataType>(
+      this->wrapped->list_data_type().atomic_data_type());
+  }
 
   friend class DataType;
+
+protected:
+  static auto GetEmptyInstance() -> const ListDataType & {
+    static auto empty_instance = ListDataType();
+    return empty_instance;
+  }
+  inline static auto Create(ProtoDataType *wrapped)
+    -> std::unique_ptr<ListDataType> {
+    return std::unique_ptr<ListDataType>(new ListDataType(wrapped));
+  }
+  ListDataType() : ProtoMessageWrapper<ProtoDataType>() {}
+
+  ListDataType(ProtoDataType *wrapped)
+    : ProtoMessageWrapper<ProtoDataType>(wrapped) {}
+
+  mutable std::unique_ptr<ReferenceDataType> reference_data_type;
 };
 
 class DataType : public ProtoMessageWrapper<ProtoDataType> {
 public:
+  DataType(ProtoDataType *wrapped)
+    : ProtoMessageWrapper<ProtoDataType>(wrapped) {}
   DataType() : ProtoMessageWrapper<ProtoDataType>() {}
+  DataType(AtomicDataType data_type) : DataType() {
+    this->wrapped->set_atomic_data_type(
+      static_cast<ProtoAtomicDataType>(data_type));
+  }
   DataType(const std::string &data_type) : DataType() {
-    this->wrapped->mutable_reference_type()->set_name(data_type);
+    this->wrapped->mutable_reference_data_type()->set_name(data_type);
   }
   [[nodiscard]] inline auto IsAtomic() const noexcept -> bool {
-    return this->wrapped->data_type_case() == DataTypeCase::kAtomicType;
+    return this->wrapped->data_type_case() == DataTypeCase::kAtomicDataType;
   }
   [[nodiscard]] inline auto AsAtomic() const noexcept -> AtomicDataType {
-    return static_cast<AtomicDataType>(this->wrapped->atomic_type());
+    return static_cast<AtomicDataType>(this->wrapped->atomic_data_type());
   }
 
   [[nodiscard]] inline auto IsReference() const noexcept -> bool {
-    return this->wrapped->data_type_case() == DataTypeCase::kReferenceType;
+    return this->wrapped->data_type_case() == DataTypeCase::kReferenceDataType;
   }
-  [[nodiscard]] inline auto AsReference() const noexcept -> ReferenceDataType {
+  [[nodiscard]] inline auto AsReference() const noexcept
+    -> const ReferenceDataType & {
     if (!IsReference()) {
-      return ReferenceDataType{};
+      return ReferenceDataType::GetEmptyInstance();
     } else if (reference_data_type == nullptr) {
-      reference_data_type = std::make_unique<ReferenceDataType>();
-      this->reference_data_type->wrapped =
-        this->wrapped->mutable_reference_type();
+      reference_data_type = std::unique_ptr<ReferenceDataType>(
+        ReferenceDataType::Create(this->wrapped).release());
     }
     return *reference_data_type;
   }
 
+  [[nodiscard]] inline auto IsList() const noexcept -> bool {
+    return this->wrapped->data_type_case() == DataTypeCase::kListDataType;
+  }
+
+  [[nodiscard]] inline auto AsList() const noexcept -> const ListDataType & {
+    if (!IsList()) {
+      return ListDataType::GetEmptyInstance();
+    } else if (list_data_type == nullptr) {
+      list_data_type = std::unique_ptr<ListDataType>(
+        ListDataType::Create(this->wrapped).release());
+    }
+    return *list_data_type;
+  }
+
   inline auto operator==(const DataType &other) const noexcept -> bool {
+    // TODO(tf) Is this safe?
     return this->wrapped->SerializeAsString() ==
            other.wrapped->SerializeAsString();
   }
 
-private:
+  friend class Entity;
+  friend class Property;
+
+protected:
   mutable std::unique_ptr<ReferenceDataType> reference_data_type;
+  mutable std::unique_ptr<ListDataType> list_data_type;
 };
 
 } // namespace caosdb::entity
diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h
index 0a66286..684872d 100644
--- a/include/caosdb/entity.h
+++ b/include/caosdb/entity.h
@@ -46,6 +46,8 @@ using ProtoMessage = caosdb::entity::v1alpha1::Message;
 using caosdb::entity::v1alpha1::EntityRole;
 using ProtoImportance = caosdb::entity::v1alpha1::Importance;
 
+const static std::string logger_name = "caosdb::entity";
+
 /**
  * The property importance.
  */
@@ -268,7 +270,10 @@ private:
  */
 class Property {
 public:
-  explicit inline Property(ProtoProperty *wrapped) : wrapped(wrapped){};
+  explicit inline Property(ProtoProperty *wrapped) : wrapped(wrapped) {
+    data_type.wrapped = this->wrapped->mutable_data_type();
+    value.wrapped = this->wrapped->mutable_value();
+  };
   Property();
 
   /**
@@ -337,6 +342,7 @@ public:
    * Set the datatype of this property.
    */
   auto SetDataType(const DataType &new_data_type) -> StatusCode;
+  auto SetDataType(const AtomicDataType new_data_type) -> StatusCode;
   auto SetDataType(const std::string &new_data_type) -> StatusCode;
 
   /**
@@ -488,8 +494,9 @@ public:
   // auto SetValue(const bool value) -> StatusCode;
 
   auto SetUnit(const std::string &unit) -> void;
-  // Currently no references or lists.
+
   auto SetDataType(const DataType &new_data_type) -> StatusCode;
+  auto SetDataType(const AtomicDataType new_data_type) -> StatusCode;
   auto SetDataType(const std::string &new_data_type) -> StatusCode;
   auto AppendProperty(const Property &property) -> void;
 
@@ -503,14 +510,14 @@ protected:
   static auto CreateProtoEntity() -> ProtoEntity *;
   auto SetId(const std::string &id) -> void;
   auto SetVersionId(const std::string &id) -> void;
+
+private:
   ProtoEntity *wrapped;
   Properties properties;
   Parents parents;
   Messages errors;
   Messages warnings;
   Messages infos;
-
-private:
   Value value;
   DataType data_type;
 };
diff --git a/include/caosdb/protobuf_helper.h b/include/caosdb/protobuf_helper.h
index e4e89ae..051a896 100644
--- a/include/caosdb/protobuf_helper.h
+++ b/include/caosdb/protobuf_helper.h
@@ -40,9 +40,9 @@ public:
   }
 
 protected:
-  ProtoMessageWrapper() {
-    this->wrapped = Arena::CreateMessage<P>(get_arena());
-  }
+  ProtoMessageWrapper()
+    : ProtoMessageWrapper(Arena::CreateMessage<P>(get_arena())) {}
+  ProtoMessageWrapper(P *wrapped) : wrapped(wrapped) {}
   P *wrapped;
 };
 
diff --git a/include/caosdb/value.h b/include/caosdb/value.h
index 158a3e8..2c3e459 100644
--- a/include/caosdb/value.h
+++ b/include/caosdb/value.h
@@ -31,6 +31,9 @@ using ProtoValue = caosdb::entity::v1alpha1::Value;
 using ValueCase = caosdb::entity::v1alpha1::Value::ValueCase;
 using ScalarValueCase = caosdb::entity::v1alpha1::ScalarValue::ScalarValueCase;
 
+class Entity;
+class Property;
+
 // Represents special values which are otherwise hard to tranfer via protobuf.
 enum SpecialValue {
   // Represent the NULL value.
@@ -80,13 +83,15 @@ public:
   }
   [[nodiscard]] inline auto AsDouble() const noexcept -> double {
     return this->wrapped->scalar_value().double_value();
-    ;
   }
 
   inline auto operator==(const Value &other) const noexcept -> bool {
     return this->wrapped->SerializeAsString() ==
            other.wrapped->SerializeAsString();
   }
+
+  friend class Entity;
+  friend class Property;
 };
 
 } // namespace caosdb::entity
diff --git a/proto b/proto
index 780c89e..f7388e1 160000
--- a/proto
+++ b/proto
@@ -1 +1 @@
-Subproject commit 780c89e86cb9c3b17171b88827aaedee3c3a9595
+Subproject commit f7388e18e0908d2b675159176f9c9a8a6f52637e
diff --git a/src/caosdb/entity.cpp b/src/caosdb/entity.cpp
index 5999196..846570e 100644
--- a/src/caosdb/entity.cpp
+++ b/src/caosdb/entity.cpp
@@ -76,7 +76,7 @@ auto Parents::Append(const Parent &parent) -> void {
   parent.wrapped = destination;
 }
 
-Property::Property() : wrapped(Property::CreateProtoProperty()) {}
+Property::Property() : Property(Property::CreateProtoProperty()) {}
 
 auto Property::CreateProtoProperty() -> ProtoProperty * {
   return google::protobuf::Arena::CreateMessage<ProtoProperty>(get_arena());
@@ -146,6 +146,10 @@ auto Property::SetDataType(const DataType &new_data_type) -> StatusCode {
   return this->data_type.CopyFrom(new_data_type);
 }
 
+auto Property::SetDataType(const AtomicDataType new_data_type) -> StatusCode {
+  return SetDataType(DataType(new_data_type));
+}
+
 auto Property::SetDataType(const std::string &new_data_type) -> StatusCode {
   return SetDataType(DataType(new_data_type));
 }
@@ -180,6 +184,7 @@ auto Entity::CreateProtoEntity() -> ProtoEntity * {
 }
 
 Entity::Entity() : wrapped(Entity::CreateProtoEntity()) {
+  data_type.wrapped = this->wrapped->mutable_data_type();
   properties.wrapped = this->wrapped->mutable_properties();
   parents.wrapped = this->wrapped->mutable_parents();
   errors.wrapped = this->wrapped->mutable_errors();
@@ -244,6 +249,10 @@ auto Entity::SetDataType(const DataType &new_data_type) -> StatusCode {
   return this->data_type.CopyFrom(new_data_type);
 }
 
+auto Entity::SetDataType(const AtomicDataType new_data_type) -> StatusCode {
+  return SetDataType(DataType(new_data_type));
+}
+
 auto Entity::SetDataType(const std::string &new_data_type) -> StatusCode {
   return SetDataType(DataType(new_data_type));
 }
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 389d5d9..4f92bbf 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -20,6 +20,7 @@
 
 # append all the test cases here (file name without the ".cpp" suffix)
 set(test_cases
+    test_data_type
     test_configuration
     test_connection
     test_entity
diff --git a/test/test_data_type.cpp b/test/test_data_type.cpp
new file mode 100644
index 0000000..2675a63
--- /dev/null
+++ b/test/test_data_type.cpp
@@ -0,0 +1,69 @@
+/*
+ *
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "caosdb/data_type.h"               // for DataType, AtomicDataType
+#include "caosdb/entity/v1alpha1/main.pb.h" // for AtomicDataType, DataType
+#include <gtest/gtest-message.h>            // for Message
+#include <gtest/gtest-test-part.h>          // for TestPartResult, SuiteApi...
+#include <gtest/gtest_pred_impl.h>          // for AssertionResult, Test
+#include <memory>                           // for allocator
+
+namespace caosdb::entity {
+using ProtoEntity = caosdb::entity::v1alpha1::Entity;
+using ProtoParent = caosdb::entity::v1alpha1::Parent;
+using ProtoDataType = caosdb::entity::v1alpha1::DataType;
+using ProtoAtomicDataType = caosdb::entity::v1alpha1::AtomicDataType;
+
+TEST(test_data_type, test_atomic) {
+  ProtoDataType proto_data_type;
+
+  for (int i = 1; i < 6; i++) {
+    proto_data_type.set_atomic_data_type(static_cast<ProtoAtomicDataType>(i));
+    DataType data_type(&proto_data_type);
+
+    EXPECT_FALSE(data_type.IsReference());
+    EXPECT_FALSE(data_type.IsList());
+    EXPECT_TRUE(data_type.IsAtomic());
+    EXPECT_EQ(data_type.AsAtomic(), static_cast<AtomicDataType>(i));
+  }
+}
+
+TEST(test_data_type, test_list_of_atomic) {
+  ProtoDataType proto_data_type;
+  auto *proto_list_data_type = proto_data_type.mutable_list_data_type();
+
+  for (int i = 1; i < 6; i++) {
+    proto_list_data_type->set_atomic_data_type(
+      static_cast<ProtoAtomicDataType>(i));
+    DataType data_type(&proto_data_type);
+
+    EXPECT_FALSE(data_type.IsReference());
+    EXPECT_FALSE(data_type.IsAtomic());
+    EXPECT_TRUE(data_type.IsList());
+    const auto &list_data_type = data_type.AsList();
+    EXPECT_TRUE(list_data_type.IsListOfAtomic());
+    EXPECT_EQ(list_data_type.GetAtomicDataType(),
+              static_cast<AtomicDataType>(i));
+  }
+}
+
+} // namespace caosdb::entity
diff --git a/test/test_entity.cpp b/test/test_entity.cpp
index 7e8ca9d..1c9e3a9 100644
--- a/test/test_entity.cpp
+++ b/test/test_entity.cpp
@@ -24,15 +24,20 @@
 #include "caosdb/entity.h"                       // for Entity, Parent, Par...
 #include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionSe...
 #include "caosdb/entity/v1alpha1/main.pb.h"      // for IdResponse, Message
-#include "caosdb/message_code.h"                 // for MessageCode
-#include "caosdb/protobuf_helper.h"              // for get_arena
-#include "caosdb/transaction.h"                  // for Transaction
-#include "caosdb/value.h"                        // for Value
-#include <google/protobuf/arena.h>               // for Arena
-#include <gtest/gtest-message.h>                 // for Message
-#include <gtest/gtest-test-part.h>               // for TestPartResult, Sui...
-#include <gtest/gtest_pred_impl.h>               // for Test, EXPECT_EQ
-#include <memory>                                // for allocator, shared_ptr
+#include "caosdb/logging.h"
+#include "caosdb/message_code.h"                      // for MessageCode
+#include "caosdb/protobuf_helper.h"                   // for get_arena
+#include "caosdb/transaction.h"                       // for Transaction
+#include "caosdb/value.h"                             // for Value
+#include <boost/log/core/record.hpp>                  // for record
+#include <boost/log/sources/record_ostream.hpp>       // for record_pump<>:...
+#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_E...
+#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_S...
+#include <google/protobuf/arena.h>                    // for Arena
+#include <gtest/gtest-message.h>                      // for Message
+#include <gtest/gtest-test-part.h> // for TestPartResult, Sui...
+#include <gtest/gtest_pred_impl.h> // for Test, EXPECT_EQ
+#include <memory>                  // for allocator, shared_ptr
 
 namespace caosdb::entity {
 using caosdb::entity::v1alpha1::IdResponse;
@@ -63,22 +68,38 @@ TEST(test_entity, test_append_parent) {
 }
 
 TEST(test_entity, test_property_setters) {
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 1";
   auto prop = Property();
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 2";
   prop.SetName("prop_name");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 3";
   prop.SetId("prop_id");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 4";
   prop.SetImportance(Importance::OBLIGATORY);
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 5";
   prop.SetValue("prop_value");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 6";
   prop.SetUnit("prop_unit");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 7";
   prop.SetDataType("prop_dtype");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 8";
 
   EXPECT_EQ(prop.GetName(), "prop_name");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 1";
   EXPECT_EQ(prop.GetId(), "prop_id");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 2";
   EXPECT_EQ(prop.GetImportance(), Importance::OBLIGATORY);
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 3";
   EXPECT_TRUE(prop.GetValue().IsString());
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 4";
   EXPECT_EQ(prop.GetValue().AsString(), "prop_value");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 5";
   EXPECT_EQ(prop.GetUnit(), "prop_unit");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 6";
   EXPECT_TRUE(prop.GetDataType().IsReference());
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 7";
   EXPECT_EQ(prop.GetDataType().AsReference().GetName(), "prop_dtype");
+  CAOSDB_LOG_DEBUG(logger_name) << "HERE 8";
 }
 
 TEST(test_entity, test_append_property) {
@@ -159,7 +180,7 @@ TEST(test_entity, test_insert_with_role) {
 
   auto entity = Entity();
   entity.SetRole(Role::PROPERTY);
-  entity.SetDataType("DOUBLE");
+  entity.SetDataType(AtomicDataType::DOUBLE);
   entity.SetName("Length");
   entity.SetUnit("m");
   entity.SetValue(5.5);
-- 
GitLab