1 /*
2  * SPDX-FileCopyrightText: 2015-2015 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 #include "rawconfig.h"
8 #include <list>
9 #include <unordered_map>
10 #include <utility>
11 #include "fcitx-utils/stringutils.h"
12 
13 namespace fcitx {
14 
15 template <typename K, typename V, class Hash = std::hash<K>,
16           class Pred = std::equal_to<K>>
17 class OrderedMap {
18 private:
19     std::list<std::pair<const K, V>> order_;
20     std::unordered_map<K, typename decltype(order_)::iterator, Hash, Pred> map_;
21 
22 public:
23     typedef decltype(map_) map_type;
24     typedef decltype(order_) list_type;
25     typedef typename map_type::key_type key_type;
26     typedef typename list_type::value_type value_type;
27     typedef typename value_type::second_type mapped_type;
28 
29     typedef typename list_type::pointer pointer;
30     typedef typename list_type::const_pointer const_pointer;
31     typedef typename list_type::reference reference;
32     typedef typename list_type::const_reference const_reference;
33     typedef typename list_type::iterator iterator;
34     typedef typename list_type::const_iterator const_iterator;
35     typedef typename list_type::size_type size_type;
36     typedef typename list_type::difference_type difference_type;
37 
38     OrderedMap() = default;
OrderedMap(const OrderedMap & other)39     OrderedMap(const OrderedMap &other) : order_(other.order_) { fillMap(); }
40 
41     /// Move constructor.
OrderedMap(OrderedMap && other)42     OrderedMap(OrderedMap &&other) noexcept { operator=(std::move(other)); }
43 
OrderedMap(std::initializer_list<value_type> l)44     OrderedMap(std::initializer_list<value_type> l) : order_(l) { fillMap(); }
45 
46     template <typename InputIterator>
OrderedMap(InputIterator first,InputIterator last)47     OrderedMap(InputIterator first, InputIterator last) : order_(first, last) {
48         fillMap();
49     }
50 
51     /// Copy assignment operator.
operator =(const OrderedMap & other)52     OrderedMap &operator=(const OrderedMap &other) {
53         order_ = list_type(other.order_.begin(), other.order_.end());
54         fillMap();
55         return *this;
56     }
57 
58     /// Move assignment operator.
operator =(OrderedMap && other)59     OrderedMap &operator=(OrderedMap &&other) noexcept {
60         using std::swap;
61         swap(order_, other.order_);
62         swap(map_, other.map_);
63         other.order_.clear();
64         other.map_.clear();
65         return *this;
66     }
67 
empty() const68     bool empty() const noexcept { return order_.empty(); }
69 
size() const70     size_type size() const noexcept { return order_.size(); }
71 
begin()72     iterator begin() noexcept { return order_.begin(); }
73 
begin() const74     const_iterator begin() const noexcept { return order_.begin(); }
75 
cbegin() const76     const_iterator cbegin() const noexcept { return order_.begin(); }
77 
end()78     iterator end() noexcept { return order_.end(); }
79 
end() const80     const_iterator end() const noexcept { return order_.end(); }
81 
cend() const82     const_iterator cend() const noexcept { return order_.end(); }
83 
84     template <typename... _Args>
emplace(_Args &&...__args)85     std::pair<iterator, bool> emplace(_Args &&...__args) {
86         order_.emplace_back(std::forward<_Args>(__args)...);
87         auto iter = std::prev(order_.end());
88         auto mapResult = map_.emplace(iter->first, iter);
89         if (mapResult.second) {
90             return {iter, true};
91         }
92         order_.erase(iter);
93         iter = mapResult.first->second;
94         return {iter, false};
95     }
96 
insert(const value_type & v)97     std::pair<iterator, bool> insert(const value_type &v) { return emplace(v); }
98 
erase(const_iterator position)99     iterator erase(const_iterator position) {
100         map_.erase(position->first);
101         return order_.erase(position);
102     }
103 
erase(iterator position)104     iterator erase(iterator position) {
105         map_.erase(position->first);
106         return order_.erase(position);
107     }
108 
erase(const key_type & k)109     size_type erase(const key_type &k) {
110         auto iter = map_.find(k);
111         if (iter != map_.end()) {
112             order_.erase(iter->second);
113             map_.erase(iter);
114             return 1;
115         }
116         return 0;
117     }
118 
clear()119     void clear() noexcept {
120         order_.clear();
121         map_.clear();
122     }
123 
find(const key_type & k)124     iterator find(const key_type &k) {
125         auto iter = map_.find(k);
126         if (iter != map_.end()) {
127             return iter->second;
128         }
129         return order_.end();
130     }
131 
find(const key_type & k) const132     const_iterator find(const key_type &k) const {
133         auto iter = map_.find(k);
134         if (iter != map_.end()) {
135             return iter->second;
136         }
137         return order_.end();
138     }
139 
count(const key_type & k) const140     size_type count(const key_type &k) const { return map_.count(k); }
141 
operator [](const key_type & k)142     mapped_type &operator[](const key_type &k) {
143         auto iter = find(k);
144         if (iter != end()) {
145             return iter->second;
146         }
147         auto result = emplace(k, mapped_type());
148         return result.first->second;
149     }
150 
operator [](key_type && k)151     mapped_type &operator[](key_type &&k) {
152         auto iter = find(k);
153         if (iter != end()) {
154             return iter->second;
155         }
156         auto result = emplace(std::move(k), value_type());
157         return result.first->second;
158     }
159 
160 private:
fillMap()161     void fillMap() {
162         for (auto iter = order_.begin(), end = order_.end(); iter != end;
163              iter++) {
164             map_[iter->first] = iter;
165         }
166     }
167 };
168 
169 class RawConfigPrivate : public QPtrHolder<RawConfig> {
170 public:
RawConfigPrivate(RawConfig * q,std::string _name)171     RawConfigPrivate(RawConfig *q, std::string _name)
172         : QPtrHolder(q), name_(std::move(_name)), lineNumber_(0) {}
RawConfigPrivate(RawConfig * q,const RawConfigPrivate & other)173     RawConfigPrivate(RawConfig *q, const RawConfigPrivate &other)
174         : QPtrHolder(q), value_(other.value_), comment_(other.comment_),
175           lineNumber_(other.lineNumber_) {}
176 
operator =(const RawConfigPrivate & other)177     RawConfigPrivate &operator=(const RawConfigPrivate &other) {
178         if (&other == this) {
179             return *this;
180         }
181         // There is no need to copy "name_", because name refers to its parent.
182         // Make a copy of value, comment, and lineNumber, because this "other"
183         // might be our parent.
184         auto value = other.value_;
185         auto comment = other.comment_;
186         auto lineNumber = other.lineNumber_;
187         OrderedMap<std::string, std::shared_ptr<RawConfig>> newSubItems;
188         for (const auto &item : other.subItems_) {
189             auto result = newSubItems[item.first] =
190                 q_func()->createSub(item.second->name());
191             *result = *item.second;
192         }
193         value_ = std::move(value);
194         comment_ = std::move(comment);
195         lineNumber_ = lineNumber;
196         detachSubItems();
197         subItems_ = std::move(newSubItems);
198         return *this;
199     }
200 
getNonexistentRawConfig(RawConfig * q,const std::string & key)201     std::shared_ptr<RawConfig> getNonexistentRawConfig(RawConfig *q,
202                                                        const std::string &key) {
203         auto result = subItems_[key] = q->createSub(key);
204         return result;
205     }
206 
207     static std::shared_ptr<const RawConfig>
getNonexistentRawConfig(const RawConfig *,const std::string &)208     getNonexistentRawConfig(const RawConfig *, const std::string &) {
209         return nullptr;
210     }
211 
212     template <typename T, typename U>
213     static std::shared_ptr<T>
getRawConfigHelper(T & that,const std::string & path,U callback)214     getRawConfigHelper(T &that, const std::string &path, U callback) {
215         auto *cur = &that;
216         std::shared_ptr<T> result;
217         for (std::string::size_type pos = 0, new_pos = path.find('/', pos);
218              pos != std::string::npos && cur;
219              pos = ((std::string::npos == new_pos) ? new_pos : (new_pos + 1)),
220                                     new_pos = path.find('/', pos)) {
221             auto key = path.substr(pos, (std::string::npos == new_pos)
222                                             ? new_pos
223                                             : (new_pos - pos));
224             auto iter = cur->d_func()->subItems_.find(key);
225             if (iter == cur->d_func()->subItems_.end()) {
226                 result = cur->d_func()->getNonexistentRawConfig(cur, key);
227             } else {
228                 result = iter->second;
229             }
230             cur = result.get();
231 
232             if (cur) {
233                 callback(*cur, path.substr(0, new_pos));
234             }
235         }
236         return result;
237     }
238 
239     template <typename T>
240     static bool
visitHelper(T & that,std::function<bool (T &,const std::string & path)> callback,bool recursive,const std::string & pathPrefix)241     visitHelper(T &that,
242                 std::function<bool(T &, const std::string &path)> callback,
243                 bool recursive, const std::string &pathPrefix) {
244         auto d = that.d_func();
245         for (const auto &pair : d->subItems_) {
246             std::shared_ptr<T> item = pair.second;
247             auto newPathPrefix = pathPrefix.empty()
248                                      ? item->name()
249                                      : pathPrefix + "/" + item->name();
250             if (!callback(*item, newPathPrefix)) {
251                 return false;
252             }
253             if (recursive) {
254                 if (!visitHelper(*item, callback, recursive, newPathPrefix)) {
255                     return false;
256                 }
257             }
258         }
259         return true;
260     }
261 
detachSubItems()262     void detachSubItems() {
263         for (const auto &pair : subItems_) {
264             pair.second->d_func()->parent_ = nullptr;
265         }
266     }
267 
268     RawConfig *parent_ = nullptr;
269     const std::string name_;
270     std::string value_;
271     std::string comment_;
272     OrderedMap<std::string, std::shared_ptr<RawConfig>> subItems_;
273     unsigned int lineNumber_;
274 };
275 
RawConfig()276 RawConfig::RawConfig() : RawConfig("") {}
277 
RawConfig(std::string name)278 RawConfig::RawConfig(std::string name)
279     : d_ptr(std::make_unique<RawConfigPrivate>(this, std::move(name))) {}
280 
~RawConfig()281 RawConfig::~RawConfig() {
282     FCITX_D();
283     d->detachSubItems();
284 }
285 
RawConfig(const RawConfig & other)286 RawConfig::RawConfig(const RawConfig &other)
287     : d_ptr(std::make_unique<RawConfigPrivate>(this, *other.d_ptr)) {
288     for (const auto &item : other.d_func()->subItems_) {
289         *get(item.first, true) = *item.second;
290     }
291 }
operator =(const RawConfig & other)292 RawConfig &RawConfig::operator=(const RawConfig &other) {
293     *d_ptr = *other.d_ptr;
294     return *this;
295 }
296 
get(const std::string & path,bool create)297 std::shared_ptr<RawConfig> RawConfig::get(const std::string &path,
298                                           bool create) {
299     auto dummy = [](const RawConfig &, const std::string &) {};
300     if (create) {
301         return RawConfigPrivate::getRawConfigHelper(*this, path, dummy);
302     }
303     return std::const_pointer_cast<RawConfig>(
304         RawConfigPrivate::getRawConfigHelper<const RawConfig>(*this, path,
305                                                               dummy));
306 }
307 
get(const std::string & path) const308 std::shared_ptr<const RawConfig> RawConfig::get(const std::string &path) const {
309     auto dummy = [](const RawConfig &, const std::string &) {};
310     return RawConfigPrivate::getRawConfigHelper(*this, path, dummy);
311 }
312 
remove(const std::string & path)313 bool RawConfig::remove(const std::string &path) {
314     auto pos = path.rfind('/');
315     auto *root = this;
316     if (pos == 0 || pos + 1 == path.size()) {
317         return false;
318     }
319 
320     if (pos != std::string::npos) {
321         root = get(path.substr(0, pos)).get();
322     }
323     return root->d_func()->subItems_.erase(path.substr(pos + 1)) > 0;
324 }
325 
removeAll()326 void RawConfig::removeAll() {
327     FCITX_D();
328     d->subItems_.clear();
329 }
330 
setValue(std::string value)331 void RawConfig::setValue(std::string value) {
332     FCITX_D();
333     d->value_ = std::move(value);
334 }
335 
setComment(std::string comment)336 void RawConfig::setComment(std::string comment) {
337     FCITX_D();
338     d->comment_ = std::move(comment);
339 }
340 
setLineNumber(unsigned int lineNumber)341 void RawConfig::setLineNumber(unsigned int lineNumber) {
342     FCITX_D();
343     d->lineNumber_ = lineNumber;
344 }
345 
name() const346 const std::string &RawConfig::name() const {
347     FCITX_D();
348     return d->name_;
349 }
350 
comment() const351 const std::string &RawConfig::comment() const {
352     FCITX_D();
353     return d->comment_;
354 }
355 
value() const356 const std::string &RawConfig::value() const {
357     FCITX_D();
358     return d->value_;
359 }
360 
lineNumber() const361 unsigned int RawConfig::lineNumber() const {
362     FCITX_D();
363     return d->lineNumber_;
364 }
365 
hasSubItems() const366 bool RawConfig::hasSubItems() const {
367     FCITX_D();
368     return !d->subItems_.empty();
369 }
370 
subItemsSize() const371 size_t RawConfig::subItemsSize() const {
372     FCITX_D();
373     return d->subItems_.size();
374 }
375 
subItems() const376 std::vector<std::string> RawConfig::subItems() const {
377     FCITX_D();
378     std::vector<std::string> result;
379     result.reserve(d->subItems_.size());
380     for (const auto &pair : d->subItems_) {
381         result.push_back(pair.first);
382     }
383     return result;
384 }
385 
parent() const386 RawConfig *RawConfig::parent() const {
387     FCITX_D();
388     return d->parent_;
389 }
390 
detach()391 std::shared_ptr<RawConfig> RawConfig::detach() {
392     FCITX_D();
393     if (!d->parent_) {
394         return {};
395     }
396     auto ref = d->parent_->get(d->name_);
397     d->parent_->d_func()->subItems_.erase(d->name_);
398     d->parent_ = nullptr;
399     return ref;
400 }
401 
visitSubItems(std::function<bool (RawConfig &,const std::string & path)> callback,const std::string & path,bool recursive,const std::string & pathPrefix)402 bool RawConfig::visitSubItems(
403     std::function<bool(RawConfig &, const std::string &path)> callback,
404     const std::string &path, bool recursive, const std::string &pathPrefix) {
405     auto *root = this;
406     std::shared_ptr<RawConfig> subItem;
407     if (!path.empty()) {
408         subItem = get(path);
409         root = subItem.get();
410     }
411 
412     if (!root) {
413         return true;
414     }
415 
416     return RawConfigPrivate::visitHelper(*root, std::move(callback), recursive,
417                                          pathPrefix);
418 }
419 
visitSubItems(std::function<bool (const RawConfig &,const std::string & path)> callback,const std::string & path,bool recursive,const std::string & pathPrefix) const420 bool RawConfig::visitSubItems(
421     std::function<bool(const RawConfig &, const std::string &path)> callback,
422     const std::string &path, bool recursive,
423     const std::string &pathPrefix) const {
424     const auto *root = this;
425     std::shared_ptr<const RawConfig> subItem;
426     if (!path.empty()) {
427         subItem = get(path);
428         root = subItem.get();
429     }
430 
431     if (!root) {
432         return true;
433     }
434 
435     return RawConfigPrivate::visitHelper(*root, std::move(callback), recursive,
436                                          pathPrefix);
437 }
438 
visitItemsOnPath(std::function<void (RawConfig &,const std::string & path)> callback,const std::string & path)439 void RawConfig::visitItemsOnPath(
440     std::function<void(RawConfig &, const std::string &path)> callback,
441     const std::string &path) {
442     RawConfigPrivate::getRawConfigHelper(*this, path, std::move(callback));
443 }
visitItemsOnPath(std::function<void (const RawConfig &,const std::string & path)> callback,const std::string & path) const444 void RawConfig::visitItemsOnPath(
445     std::function<void(const RawConfig &, const std::string &path)> callback,
446     const std::string &path) const {
447     RawConfigPrivate::getRawConfigHelper(*this, path, std::move(callback));
448 }
449 
createSub(std::string name)450 std::shared_ptr<RawConfig> RawConfig::createSub(std::string name) {
451     struct RawSubConfig : public RawConfig {
452         RawSubConfig(RawConfig *parent, std::string name)
453             : RawConfig(std::move(name)) {
454             FCITX_D();
455             d->parent_ = parent;
456         }
457     };
458     return std::make_shared<RawSubConfig>(this, std::move(name));
459 }
460 
operator <<(LogMessageBuilder & log,const RawConfig & config)461 LogMessageBuilder &operator<<(LogMessageBuilder &log, const RawConfig &config) {
462     log << "RawConfig(=" << config.value();
463     config.visitSubItems(
464         [&log](const RawConfig &subConfig, const std::string &path) {
465             log << ", " << path << "=" << subConfig.value();
466             return true;
467         },
468         "", true);
469     log << ")";
470     return log;
471 }
472 } // namespace fcitx
473