Add the ability to remove a component from component tree

This functionality will be neaded on Brillo side to remove components
added by vendor daemons when those daemons exit (normally or abnormally).
This will allow those daemons to re-add the same component when they get
restarted.

Change-Id: Ida350cfa38d4f1265d1e86fccca893cdf7f5030c
Reviewed-on: https://weave-review.googlesource.com/2087
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/include/weave/device.h b/include/weave/device.h
index 069e688..05e3b78 100644
--- a/include/weave/device.h
+++ b/include/weave/device.h
@@ -62,6 +62,10 @@
                             const std::vector<std::string>& traits,
                             ErrorPtr* error) = 0;
 
+  // Removes an existing component instance from device.
+  virtual bool RemoveComponent(const std::string& name,
+                               ErrorPtr* error) = 0;
+
   // Sets callback which is called when new components are added.
   virtual void AddComponentTreeChangedCallback(
       const base::Closure& callback) = 0;
diff --git a/include/weave/test/mock_device.h b/include/weave/test/mock_device.h
index ddd3f59..2f380e0 100644
--- a/include/weave/test/mock_device.h
+++ b/include/weave/test/mock_device.h
@@ -30,6 +30,7 @@
                bool(const std::string& name,
                     const std::vector<std::string>& traits,
                     ErrorPtr* error));
+  MOCK_METHOD2(RemoveComponent, bool(const std::string& name, ErrorPtr* error));
   MOCK_METHOD1(AddComponentTreeChangedCallback,
                void(const base::Closure& callback));
   MOCK_CONST_METHOD0(GetComponents, const base::DictionaryValue&());
diff --git a/src/component_manager.h b/src/component_manager.h
index cf16720..832b274 100644
--- a/src/component_manager.h
+++ b/src/component_manager.h
@@ -85,6 +85,22 @@
                                      const std::vector<std::string>& traits,
                                      ErrorPtr* error) = 0;
 
+  // Removes an existing component instance from device.
+  // |path| is a path to the parent component (or empty string if a root-level
+  // component is being removed).
+  // |name| is a name of the component to be removed.
+  virtual bool RemoveComponent(const std::string& path,
+                               const std::string& name,
+                               ErrorPtr* error) = 0;
+
+  // Removes an element from component array.
+  // |path| is a path to the parent component.
+  // |index| is a zero-based element index in the component array.
+  virtual bool RemoveComponentArrayItem(const std::string& path,
+                                        const std::string& name,
+                                        size_t index,
+                                        ErrorPtr* error) = 0;
+
   // Sets callback which is called when new components are added.
   virtual void AddComponentTreeChangedCallback(
       const base::Closure& callback) = 0;
diff --git a/src/component_manager_impl.cc b/src/component_manager_impl.cc
index b1f3289..3f6ebf9 100644
--- a/src/component_manager_impl.cc
+++ b/src/component_manager_impl.cc
@@ -99,6 +99,63 @@
   return true;
 }
 
+bool ComponentManagerImpl::RemoveComponent(const std::string& path,
+                                           const std::string& name,
+                                           ErrorPtr* error) {
+  base::DictionaryValue* root = &components_;
+  if (!path.empty()) {
+    root = FindComponentGraftNode(path, error);
+    if (!root)
+      return false;
+  }
+
+  if (!root->RemoveWithoutPathExpansion(name, nullptr)) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kInvalidState,
+                       "Component '%s' does not exist at path '%s'",
+                       name.c_str(), path.c_str());
+    return false;
+  }
+
+  for (const auto& cb : on_componet_tree_changed_)
+    cb.Run();
+  return true;
+}
+
+bool ComponentManagerImpl::RemoveComponentArrayItem(const std::string& path,
+                                                    const std::string& name,
+                                                    size_t index,
+                                                    ErrorPtr* error) {
+  base::DictionaryValue* root = &components_;
+  if (!path.empty()) {
+    root = FindComponentGraftNode(path, error);
+    if (!root)
+      return false;
+  }
+
+  base::ListValue* array_value = nullptr;
+  if (!root->GetListWithoutPathExpansion(name, &array_value)) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kInvalidState,
+                       "There is no component array named '%s' at path '%s'",
+                       name.c_str(), path.c_str());
+    return false;
+  }
+
+  if (!array_value->Remove(index, nullptr)) {
+    Error::AddToPrintf(
+        error, FROM_HERE, errors::commands::kDomain,
+        errors::commands::kInvalidState,
+        "Component array '%s' at path '%s' does not have an element %zu",
+        name.c_str(), path.c_str(), index);
+    return false;
+  }
+
+  for (const auto& cb : on_componet_tree_changed_)
+    cb.Run();
+  return true;
+}
+
 void ComponentManagerImpl::AddComponentTreeChangedCallback(
     const base::Closure& callback) {
   on_componet_tree_changed_.push_back(callback);
diff --git a/src/component_manager_impl.h b/src/component_manager_impl.h
index 97d302d..8c4ad16 100644
--- a/src/component_manager_impl.h
+++ b/src/component_manager_impl.h
@@ -47,6 +47,23 @@
                              const std::vector<std::string>& traits,
                              ErrorPtr* error) override;
 
+  // Removes an existing component instance from device.
+  // |path| is a path to the parent component (or empty string if a root-level
+  // component is being removed).
+  // |name| is a name of the component to be removed.
+  bool RemoveComponent(const std::string& path,
+                       const std::string& name,
+                       ErrorPtr* error) override;
+
+  // Removes an element from component array.
+  // |path| is a path to the parent component.
+  // |name| is an array root element inside the child components.
+  // |index| is a zero-based element index in the component array.
+  bool RemoveComponentArrayItem(const std::string& path,
+                                const std::string& name,
+                                size_t index,
+                                ErrorPtr* error) override;
+
   // Sets callback which is called when new components are added.
   void AddComponentTreeChangedCallback(const base::Closure& callback) override;
 
diff --git a/src/component_manager_unittest.cc b/src/component_manager_unittest.cc
index 6b660b1..519b80a 100644
--- a/src/component_manager_unittest.cc
+++ b/src/component_manager_unittest.cc
@@ -519,6 +519,48 @@
   EXPECT_JSON_EQ(kExpected, manager_.GetComponents());
 }
 
+TEST_F(ComponentManagerTest, RemoveComponent) {
+  CreateTestComponentTree(&manager_);
+  EXPECT_TRUE(manager_.RemoveComponent("comp1.comp2[1].comp3", "comp4",
+                                       nullptr));
+  const char kExpected1[] = R"({
+    "comp1": {
+      "traits": [ "t1" ],
+      "components": {
+        "comp2": [
+          {
+            "traits": [ "t2" ]
+          },
+          {
+            "traits": [ "t3" ],
+            "components": {
+              "comp3": {
+                "traits": [ "t4" ],
+                "components": {}
+              }
+            }
+          }
+        ]
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected1, manager_.GetComponents());
+  EXPECT_TRUE(manager_.RemoveComponentArrayItem("comp1", "comp2", 1, nullptr));
+  const char kExpected2[] = R"({
+    "comp1": {
+      "traits": [ "t1" ],
+      "components": {
+        "comp2": [
+          {
+            "traits": [ "t2" ]
+          }
+        ]
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected2, manager_.GetComponents());
+}
+
 TEST_F(ComponentManagerTest, AddComponentExist) {
   EXPECT_TRUE(manager_.AddComponent("", "comp1", {}, nullptr));
   EXPECT_FALSE(manager_.AddComponent("", "comp1", {}, nullptr));
@@ -548,6 +590,10 @@
   EXPECT_EQ(5, count);
   EXPECT_TRUE(manager_.AddComponentArrayItem("comp1", "comp3", {}, nullptr));
   EXPECT_EQ(6, count);
+  EXPECT_TRUE(manager_.RemoveComponentArrayItem("comp1", "comp3", 1, nullptr));
+  EXPECT_EQ(7, count);
+  EXPECT_TRUE(manager_.RemoveComponent("", "comp1", nullptr));
+  EXPECT_EQ(8, count);
   // Make sure both callbacks were called the same number of times.
   EXPECT_EQ(count2, count);
 }
diff --git a/src/device_manager.cc b/src/device_manager.cc
index 67a2c86..1158df7 100644
--- a/src/device_manager.cc
+++ b/src/device_manager.cc
@@ -106,6 +106,10 @@
   return component_manager_->AddComponent("", name, traits, error);
 }
 
+bool DeviceManager::RemoveComponent(const std::string& name, ErrorPtr* error) {
+  return component_manager_->RemoveComponent("", name, error);
+}
+
 void DeviceManager::AddComponentTreeChangedCallback(
     const base::Closure& callback) {
   component_manager_->AddComponentTreeChangedCallback(callback);
diff --git a/src/device_manager.h b/src/device_manager.h
index d21f398..d40ba8e 100644
--- a/src/device_manager.h
+++ b/src/device_manager.h
@@ -43,6 +43,7 @@
   bool AddComponent(const std::string& name,
                     const std::vector<std::string>& traits,
                     ErrorPtr* error) override;
+  bool RemoveComponent(const std::string& name, ErrorPtr* error) override;
   void AddComponentTreeChangedCallback(const base::Closure& callback) override;
   const base::DictionaryValue& GetComponents() const override;
   bool SetStatePropertiesFromJson(const std::string& component,
diff --git a/src/mock_component_manager.h b/src/mock_component_manager.h
index 08c1e59..addd6f0 100644
--- a/src/mock_component_manager.h
+++ b/src/mock_component_manager.h
@@ -28,6 +28,15 @@
                     const std::string& name,
                     const std::vector<std::string>& traits,
                     ErrorPtr* error));
+  MOCK_METHOD3(RemoveComponent,
+               bool(const std::string& path,
+                    const std::string& name,
+                    ErrorPtr* error));
+  MOCK_METHOD4(RemoveComponentArrayItem,
+               bool(const std::string& path,
+                    const std::string& name,
+                    size_t index,
+                    ErrorPtr* error));
   MOCK_METHOD1(AddComponentTreeChangedCallback,
                void(const base::Closure& callback));
   MOCK_METHOD1(MockAddCommand, void(CommandInstance* command_instance));