1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "object_hierarchy_observer.h"
17
18#include <algorithm>
19
20#include <meta/api/iteration.h>
21#include <meta/api/make_callback.h>
22#include <meta/api/property/property_event_handler.h>
23#include <meta/api/util.h>
24#include <meta/base/interface_utils.h>
25#include <meta/interface/intf_containable.h>
26#include <meta/interface/intf_content.h>
27
28META_BEGIN_NAMESPACE()
29
30// ContainerChangeListener
31
32ObjectChangeListener::ObjectChangeListener(const IObject::Ptr& object, HierarchyChangeObjectType myType,
33    const IObject::WeakPtr& parent, ObjectHierarchyObserver* observer, HierarchyChangeModeValue mode)
34    : object_(object), type_(myType), parent_(parent), observer_(observer)
35{
36    CORE_ASSERT(observer_ && object);
37    Subscribe(mode);
38}
39
40ObjectChangeListener::~ObjectChangeListener()
41{
42    Unsubscribe();
43}
44
45void ObjectChangeListener::SubscribeContainer(const IObject::Ptr& object)
46{
47    if (const auto container = interface_cast<IContainer>(object)) {
48        if (auto trans = interface_cast<IContainerPreTransaction>(container)) {
49            handlers_.emplace_back(trans->OnAdding(), [this](const ChildChangedInfo& info, bool&) {
50                NotifyContainerChangeOp(info, HierarchyChangeType::ADDING, HierarchyChangeObjectType::CHILD);
51            });
52            handlers_.emplace_back(trans->OnRemoving(), [this](const ChildChangedInfo& info, bool&) {
53                NotifyContainerChangeOp(info, HierarchyChangeType::REMOVING, HierarchyChangeObjectType::CHILD);
54            });
55            containerPreTransaction_ = true;
56        }
57
58        handlers_.emplace_back(container->OnAdded(), [this](const ChildChangedInfo& info) {
59            NotifyContainerChangeOp(info, HierarchyChangeType::ADDED, HierarchyChangeObjectType::CHILD);
60        });
61        handlers_.emplace_back(container->OnRemoved(), [this](const ChildChangedInfo& info) {
62            NotifyContainerChangeOp(info, HierarchyChangeType::REMOVED, HierarchyChangeObjectType::CHILD);
63        });
64        handlers_.emplace_back(container->OnMoved(), [this](const ChildMovedInfo& info) {
65            NotifyContainerMoveOp(info, HierarchyChangeType::MOVED, HierarchyChangeObjectType::CHILD);
66        });
67    }
68}
69
70void ObjectChangeListener::SubscribeAttachment(const IObject::Ptr& object)
71{
72    if (const auto attach = interface_cast<IAttach>(object)) {
73        if (const auto container = attach->GetAttachmentContainer(true)) {
74            if (const auto trans = interface_cast<IContainerPreTransaction>(container)) {
75                handlers_.emplace_back(trans->OnAdding(), [this](const ChildChangedInfo& info, bool&) {
76                    NotifyContainerChangeOp(info, HierarchyChangeType::ADDING, HierarchyChangeObjectType::ATTACHMENT);
77                });
78                handlers_.emplace_back(trans->OnRemoving(), [this](const ChildChangedInfo& info, bool&) {
79                    NotifyContainerChangeOp(info, HierarchyChangeType::REMOVING, HierarchyChangeObjectType::ATTACHMENT);
80                });
81                attachmentPreTransaction_ = true;
82            }
83            handlers_.emplace_back(container->OnAdded(), [this](const ChildChangedInfo& info) {
84                NotifyContainerChangeOp(info, HierarchyChangeType::ADDED, HierarchyChangeObjectType::ATTACHMENT);
85            });
86            handlers_.emplace_back(container->OnRemoved(), [this](const ChildChangedInfo& info) {
87                NotifyContainerChangeOp(info, HierarchyChangeType::REMOVED, HierarchyChangeObjectType::ATTACHMENT);
88            });
89            handlers_.emplace_back(container->OnMoved(), [this](const ChildMovedInfo& info) {
90                NotifyContainerMoveOp(info, HierarchyChangeType::MOVED, HierarchyChangeObjectType::ATTACHMENT);
91            });
92        }
93    }
94}
95
96bool ObjectChangeListener::Subscribe(HierarchyChangeModeValue mode)
97{
98    if (const auto object = object_.lock()) {
99        // Figure out which hierarchical interfaces our target object implements and add listeners based on that
100        if (mode & HierarchyChangeMode::NOTIFY_CONTAINER) {
101            SubscribeContainer(object);
102        }
103        if (mode & HierarchyChangeMode::NOTIFY_CONTENT) {
104            if (const auto content = interface_cast<IContent>(object)) {
105                content_ = META_NS::GetValue(content->Content());
106                handlers_.emplace_back(
107                    content->Content()->OnChanged(), MakeCallback<IOnChanged>([this]() { NotifyContentChangeOp(); }));
108            }
109        }
110        if (mode & HierarchyChangeMode::NOTIFY_ATTACHMENT) {
111            SubscribeAttachment(object);
112        }
113        if (mode & HierarchyChangeMode::NOTIFY_OBJECT) {
114            if (auto i = interface_pointer_cast<INotifyOnChange>(object)) {
115                handlers_.emplace_back(i->OnChanged(), MakeCallback<IOnChanged>([this]() { NotifyObjectChangedOp(); }));
116            }
117        }
118        return true;
119    }
120    return false;
121}
122
123void ObjectChangeListener::Unsubscribe()
124{
125    handlers_.clear();
126    content_.reset();
127}
128
129void ObjectChangeListener::NotifyObjectChangedOp()
130{
131    if (auto object = object_.lock()) {
132        HierarchyChangedInfo change;
133        change.object = object;
134        change.change = HierarchyChangeType::CHANGED;
135        change.objectType = type_;
136        change.parent = parent_;
137        observer_->HierarchyChanged(change, this);
138    }
139}
140
141void ObjectChangeListener::NotifyContainerChangeOp(
142    const ChildChangedInfo& info, HierarchyChangeType operation, HierarchyChangeObjectType objectType)
143{
144    HierarchyChangedInfo change;
145    change.object = info.object;
146    change.change = operation;
147    change.objectType = objectType;
148    change.index = info.index;
149    change.parent = object_;
150    if ((objectType == HierarchyChangeObjectType::CHILD && !containerPreTransaction_) ||
151        (objectType == HierarchyChangeObjectType::ATTACHMENT && !attachmentPreTransaction_)) {
152        // Our target does not support pre transaction notifications, generate ones.
153        if (operation == HierarchyChangeType::ADDED) {
154            change.change = HierarchyChangeType::ADDING;
155            observer_->HierarchyChanged(change, this);
156            change.change = operation;
157        } else if (operation == HierarchyChangeType::REMOVED) {
158            change.change = HierarchyChangeType::REMOVING;
159            observer_->HierarchyChanged(change, this);
160            change.change = operation;
161        }
162    }
163    observer_->HierarchyChanged(change, this);
164}
165
166void ObjectChangeListener::NotifyContainerMoveOp(
167    const ChildMovedInfo& info, HierarchyChangeType operation, HierarchyChangeObjectType objectType)
168{
169    HierarchyChangedInfo change;
170    change.object = info.object;
171    change.change = operation;
172    change.objectType = objectType;
173    change.parent = object_;
174    observer_->HierarchyChanged(change, this);
175}
176
177void ObjectChangeListener::NotifyContentChangeOp()
178{
179    if (const auto content = interface_pointer_cast<IContent>(object_)) {
180        const auto newContent = GetValue(content->Content());
181        const auto oldContent = content_;
182
183        HierarchyChangedInfo change;
184        change.objectType = HierarchyChangeObjectType::CONTENT;
185        change.parent = object_;
186
187        if (oldContent != newContent) {
188            if (oldContent) {
189                change.object = oldContent;
190                change.change = HierarchyChangeType::REMOVING;
191                observer_->HierarchyChanged(change, this);
192                change.change = HierarchyChangeType::REMOVED;
193                observer_->HierarchyChanged(change, this);
194            }
195            content_ = newContent;
196            if (newContent) {
197                change.object = newContent;
198                change.change = HierarchyChangeType::ADDING;
199                observer_->HierarchyChanged(change, this);
200                change.change = HierarchyChangeType::ADDED;
201                observer_->HierarchyChanged(change, this);
202            }
203        }
204    }
205}
206
207// ContainerObserver
208
209bool ObjectHierarchyObserver::Build(const IMetadata::Ptr& p)
210{
211    bool ret = Super::Build(p);
212    if (ret) {
213        // we don't want to serialise observers
214        META_NS::SetObjectFlags(GetSelf(), ObjectFlagBits::SERIALIZE, false);
215    }
216    return ret;
217}
218
219void ObjectHierarchyObserver::Destroy()
220{
221    ClearSubscriptions();
222}
223
224void ObjectHierarchyObserver::SetTarget(const IObject::Ptr& root, HierarchyChangeModeValue mode)
225{
226    ClearSubscriptions();
227    mode_ = mode;
228    root_ = root;
229
230    if (auto i = interface_cast<IAttach>(root)) {
231        i->Attach(GetSelf<IAttachment>());
232    }
233
234    Subscribe(root, HierarchyChangeObjectType::ROOT);
235}
236
237void ObjectHierarchyObserver::ClearSubscriptions()
238{
239    // Delay subscription removal until outside lock
240    decltype(subscriptions_) subs;
241    decltype(immediateChildren_) immes;
242    {
243        std::unique_lock lock(mutex_);
244        subs = BASE_NS::move(subscriptions_);
245        immes = BASE_NS::move(immediateChildren_);
246    }
247    subs.clear();
248    immes.clear();
249}
250
251IObject::Ptr ObjectHierarchyObserver::GetTarget() const
252{
253    return root_.lock();
254}
255
256BASE_NS::vector<IObject::Ptr> ObjectHierarchyObserver::GetAllObserved() const
257{
258    std::shared_lock lock(mutex_);
259    BASE_NS::vector<IObject::Ptr> observed;
260    observed.reserve(subscriptions_.size());
261    for (auto&& sub : subscriptions_) {
262        if (auto object = sub.second.object_.lock()) {
263            observed.emplace_back(BASE_NS::move(object));
264        }
265    }
266    return observed;
267}
268
269void ObjectHierarchyObserver::AddImmediateChild(const HierarchyChangedInfo& info)
270{
271    std::unique_lock lock(mutex_);
272    if (info.index < immediateChildren_.size()) {
273        immediateChildren_.insert(
274            immediateChildren_.begin() + info.index, ImmediateChild { info.object, info.objectType });
275    } else {
276        immediateChildren_.push_back(ImmediateChild { info.object, info.objectType });
277    }
278}
279
280void ObjectHierarchyObserver::RemoveImmediateChild(const HierarchyChangedInfo& info)
281{
282    std::unique_lock lock(mutex_);
283    auto it = immediateChildren_.begin();
284    for (; it != immediateChildren_.end() && it->object.lock() != info.object; ++it) {
285    }
286    if (it != immediateChildren_.end()) {
287        immediateChildren_.erase(it);
288    }
289}
290
291void ObjectHierarchyObserver::HierarchyChanged(const HierarchyChangedInfo& info, ObjectChangeListener* listener)
292{
293    if (info.object != GetSelf()) {
294        META_ACCESS_EVENT(OnHierarchyChanged)->Invoke(info);
295        if (info.objectType == HierarchyChangeObjectType::CHILD ||
296            info.objectType == HierarchyChangeObjectType::CONTENT) {
297            // We do not listen to attachments (other than monitoring for changes in an object's attachment list)
298            if (info.change == HierarchyChangeType::ADDING) {
299                Subscribe(info.object, info.objectType, info.parent);
300                if (keepTrackOfImmediate_ && listener->GetType() == HierarchyChangeObjectType::ROOT) {
301                    AddImmediateChild(info);
302                }
303            } else if (info.change == HierarchyChangeType::REMOVED) {
304                Unsubscribe(info.object);
305                if (keepTrackOfImmediate_ && listener->GetType() == HierarchyChangeObjectType::ROOT) {
306                    RemoveImmediateChild(info);
307                }
308            }
309        }
310    }
311}
312
313void ObjectHierarchyObserver::Subscribe(
314    const IObject::Ptr& root, HierarchyChangeObjectType type, const IObject::WeakPtr& parent)
315{
316    if (!root) {
317        return;
318    }
319
320    AddSubscription(root, type, parent);
321
322    if (const auto container = interface_cast<IContainer>(root)) {
323        for (auto&& child : container->GetAll()) {
324            if (keepTrackOfImmediate_ && type == HierarchyChangeObjectType::ROOT) {
325                std::unique_lock lock(mutex_);
326                immediateChildren_.push_back(ImmediateChild { child, HierarchyChangeObjectType::CHILD });
327            }
328            Subscribe(child, HierarchyChangeObjectType::CHILD, root);
329        }
330    }
331    if (const auto content = interface_cast<IContent>(root)) {
332        if (auto object = GetValue(content->Content())) {
333            if (keepTrackOfImmediate_ && type == HierarchyChangeObjectType::ROOT) {
334                std::unique_lock lock(mutex_);
335                immediateChildren_.push_back(ImmediateChild { object, HierarchyChangeObjectType::CONTENT });
336            }
337            Subscribe(object, HierarchyChangeObjectType::CONTENT, root);
338        }
339    }
340}
341
342void ObjectHierarchyObserver::AddSubscription(
343    const IObject::Ptr& object, HierarchyChangeObjectType type, const IObject::WeakPtr& parent)
344{
345    auto listener = BASE_NS::make_unique<ObjectChangeListener>(object, type, parent, this, mode_);
346
347    {
348        std::unique_lock lock(mutex_);
349        if (subscriptions_.find(listener.get()) != subscriptions_.end()) {
350            CORE_LOG_E(
351                "Duplicate event subscription for %s:%s", object->GetClassName().data(), object->GetName().data());
352        }
353        subscriptions_.insert({ listener.get(), Subscription(object, BASE_NS::move(listener)) });
354    }
355}
356
357void ObjectHierarchyObserver::Unsubscribe(const IObject::Ptr& root)
358{
359    if (!root) {
360        return;
361    }
362
363    RemoveSubscription(root);
364
365    if (root->GetInterface(IIterable::UID)) {
366        ForEachShared(
367            root, [this](const IObject::Ptr& object) { RemoveSubscription(object); },
368            TraversalType::DEPTH_FIRST_PRE_ORDER);
369    } else {
370        if (const auto container = interface_cast<IContainer>(root)) {
371            for (auto&& child : container->GetAll()) {
372                Unsubscribe(child);
373            }
374        }
375        if (const auto content = interface_cast<IContent>(root)) {
376            if (auto object = GetValue(content->Content())) {
377                Unsubscribe(object);
378            }
379        }
380    }
381}
382
383void ObjectHierarchyObserver::RemoveSubscription(const IObject::Ptr& object)
384{
385    // Delay subscription destruction until we're out of the lock
386    BASE_NS::vector<Subscription> subs;
387    {
388        std::unique_lock lock(mutex_);
389        auto it = subscriptions_.begin();
390        while (it != subscriptions_.end()) {
391            const auto o = it->second.object_.lock();
392            if (!o || o == object) {
393                subs.emplace_back(BASE_NS::move(it->second));
394                it = subscriptions_.erase(it);
395            } else {
396                ++it;
397            }
398        }
399    }
400    subs.clear();
401}
402
403void ObjectHierarchyObserver::NotifyOnDetach()
404{
405    // If we were attached to the target and we are getting detached when the target already died, lets
406    // notify about the removing the immediate children as those are not otherwise notified when destroying
407    // container
408    decltype(immediateChildren_) ic;
409    {
410        std::unique_lock lock(mutex_);
411        ic = BASE_NS::move(immediateChildren_);
412    }
413    if (root_.expired()) {
414        for (auto&& c : ic) {
415            if (auto o = c.object.lock()) {
416                // Generate pre transaction notifications (even if coming in wrong time).
417                HierarchyChangedInfo info { o, HierarchyChangeType::REMOVING, c.type, size_t(-1), nullptr };
418                META_ACCESS_EVENT(OnHierarchyChanged)->Invoke(info);
419                info.change = HierarchyChangeType::REMOVED;
420                META_ACCESS_EVENT(OnHierarchyChanged)->Invoke(info);
421            }
422        }
423    }
424}
425
426bool ObjectHierarchyObserver::Attaching(const META_NS::IAttach::Ptr& target, const META_NS::IObject::Ptr& dataContext)
427{
428    META_ACCESS_PROPERTY(AttachedTo)->SetValue(target);
429    META_ACCESS_PROPERTY(DataContext)->SetValue(dataContext);
430    {
431        std::unique_lock lock(mutex_);
432        keepTrackOfImmediate_ = true;
433    }
434    return true;
435}
436bool ObjectHierarchyObserver::Detaching(const META_NS::IAttach::Ptr& target)
437{
438    NotifyOnDetach();
439    {
440        std::unique_lock lock(mutex_);
441        keepTrackOfImmediate_ = false;
442    }
443    META_ACCESS_PROPERTY(AttachedTo)->SetValue({});
444    META_ACCESS_PROPERTY(DataContext)->SetValue({});
445    return true;
446}
447
448META_END_NAMESPACE()
449