xref: /aosp_15_r20/external/libchrome/base/containers/id_map.h (revision 635a864187cb8b6c713ff48b7e790a6b21769273)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_CONTAINERS_ID_MAP_H_
6 #define BASE_CONTAINERS_ID_MAP_H_
7 
8 #include <stddef.h>
9 #include <stdint.h>
10 
11 #include <memory>
12 #include <set>
13 #include <type_traits>
14 #include <unordered_map>
15 #include <utility>
16 
17 #include "base/containers/flat_set.h"
18 #include "base/logging.h"
19 #include "base/macros.h"
20 #include "base/sequence_checker.h"
21 
22 namespace base {
23 
24 // This object maintains a list of IDs that can be quickly converted to
25 // pointers to objects. It is implemented as a hash table, optimized for
26 // relatively small data sets (in the common case, there will be exactly one
27 // item in the list).
28 //
29 // Items can be inserted into the container with arbitrary ID, but the caller
30 // must ensure they are unique. Inserting IDs and relying on automatically
31 // generated ones is not allowed because they can collide.
32 
33 // The map's value type (the V param) can be any dereferenceable type, such as a
34 // raw pointer or smart pointer
35 template <typename V, typename K = int32_t>
36 class IDMap final {
37  public:
38   using KeyType = K;
39 
40  private:
41   using T = typename std::remove_reference<decltype(*V())>::type;
42 
43   using HashTable = std::unordered_map<KeyType, V>;
44 
45  public:
IDMap()46   IDMap() : iteration_depth_(0), next_id_(1), check_on_null_data_(false) {
47     // A number of consumers of IDMap create it on one thread but always
48     // access it from a different, but consistent, thread (or sequence)
49     // post-construction. The first call to CalledOnValidSequence() will re-bind
50     // it.
51     DETACH_FROM_SEQUENCE(sequence_checker_);
52   }
53 
~IDMap()54   ~IDMap() {
55     // Many IDMap's are static, and hence will be destroyed on the main
56     // thread. However, all the accesses may take place on another thread (or
57     // sequence), such as the IO thread. Detaching again to clean this up.
58     DETACH_FROM_SEQUENCE(sequence_checker_);
59   }
60 
61   // Sets whether Add and Replace should DCHECK if passed in NULL data.
62   // Default is false.
set_check_on_null_data(bool value)63   void set_check_on_null_data(bool value) { check_on_null_data_ = value; }
64 
65   // Adds a view with an automatically generated unique ID. See AddWithID.
Add(V data)66   KeyType Add(V data) { return AddInternal(std::move(data)); }
67 
68   // Adds a new data member with the specified ID. The ID must not be in
69   // the list. The caller either must generate all unique IDs itself and use
70   // this function, or allow this object to generate IDs and call Add. These
71   // two methods may not be mixed, or duplicate IDs may be generated.
AddWithID(V data,KeyType id)72   void AddWithID(V data, KeyType id) { AddWithIDInternal(std::move(data), id); }
73 
Remove(KeyType id)74   void Remove(KeyType id) {
75     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
76     typename HashTable::iterator i = data_.find(id);
77     if (i == data_.end() || IsRemoved(id)) {
78       NOTREACHED() << "Attempting to remove an item not in the list";
79       return;
80     }
81 
82     if (iteration_depth_ == 0) {
83       data_.erase(i);
84     } else {
85       removed_ids_.insert(id);
86     }
87   }
88 
89   // Replaces the value for |id| with |new_data| and returns the existing value.
90   // Should only be called with an already added id.
Replace(KeyType id,V new_data)91   V Replace(KeyType id, V new_data) {
92     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
93     DCHECK(!check_on_null_data_ || new_data);
94     typename HashTable::iterator i = data_.find(id);
95     DCHECK(i != data_.end());
96     DCHECK(!IsRemoved(id));
97 
98     using std::swap;
99     swap(i->second, new_data);
100     return new_data;
101   }
102 
Clear()103   void Clear() {
104     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
105     if (iteration_depth_ == 0) {
106       data_.clear();
107     } else {
108       removed_ids_.reserve(data_.size());
109       removed_ids_.insert(KeyIterator(data_.begin()), KeyIterator(data_.end()));
110     }
111   }
112 
IsEmpty()113   bool IsEmpty() const {
114     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
115     return size() == 0u;
116   }
117 
Lookup(KeyType id)118   T* Lookup(KeyType id) const {
119     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
120     typename HashTable::const_iterator i = data_.find(id);
121     if (i == data_.end() || !i->second || IsRemoved(id))
122       return nullptr;
123     return &*i->second;
124   }
125 
size()126   size_t size() const {
127     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
128     return data_.size() - removed_ids_.size();
129   }
130 
131 #if defined(UNIT_TEST)
iteration_depth()132   int iteration_depth() const {
133     return iteration_depth_;
134   }
135 #endif  // defined(UNIT_TEST)
136 
137   // It is safe to remove elements from the map during iteration. All iterators
138   // will remain valid.
139   template<class ReturnType>
140   class Iterator {
141    public:
Iterator(IDMap<V,K> * map)142     Iterator(IDMap<V, K>* map) : map_(map), iter_(map_->data_.begin()) {
143       Init();
144     }
145 
Iterator(const Iterator & iter)146     Iterator(const Iterator& iter)
147         : map_(iter.map_),
148           iter_(iter.iter_) {
149       Init();
150     }
151 
152     const Iterator& operator=(const Iterator& iter) {
153       map_ = iter.map;
154       iter_ = iter.iter;
155       Init();
156       return *this;
157     }
158 
~Iterator()159     ~Iterator() {
160       DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
161 
162       // We're going to decrement iteration depth. Make sure it's greater than
163       // zero so that it doesn't become negative.
164       DCHECK_LT(0, map_->iteration_depth_);
165 
166       if (--map_->iteration_depth_ == 0)
167         map_->Compact();
168     }
169 
IsAtEnd()170     bool IsAtEnd() const {
171       DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
172       return iter_ == map_->data_.end();
173     }
174 
GetCurrentKey()175     KeyType GetCurrentKey() const {
176       DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
177       return iter_->first;
178     }
179 
GetCurrentValue()180     ReturnType* GetCurrentValue() const {
181       DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
182       if (!iter_->second || map_->IsRemoved(iter_->first))
183         return nullptr;
184       return &*iter_->second;
185     }
186 
Advance()187     void Advance() {
188       DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
189       ++iter_;
190       SkipRemovedEntries();
191     }
192 
193    private:
Init()194     void Init() {
195       DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
196       ++map_->iteration_depth_;
197       SkipRemovedEntries();
198     }
199 
SkipRemovedEntries()200     void SkipRemovedEntries() {
201       while (iter_ != map_->data_.end() && map_->IsRemoved(iter_->first))
202         ++iter_;
203     }
204 
205     IDMap<V, K>* map_;
206     typename HashTable::const_iterator iter_;
207   };
208 
209   typedef Iterator<T> iterator;
210   typedef Iterator<const T> const_iterator;
211 
212  private:
213   // Transforms a map iterator to an iterator on the keys of the map.
214   // Used by Clear() to populate |removed_ids_| in bulk.
215   struct KeyIterator : std::iterator<std::forward_iterator_tag, KeyType> {
216     using inner_iterator = typename HashTable::iterator;
217     inner_iterator iter_;
218 
KeyIteratorKeyIterator219     KeyIterator(inner_iterator iter) : iter_(iter) {}
220     KeyType operator*() const { return iter_->first; }
221     KeyIterator& operator++() {
222       ++iter_;
223       return *this;
224     }
225     KeyIterator operator++(int) { return KeyIterator(iter_++); }
226     bool operator==(const KeyIterator& other) const {
227       return iter_ == other.iter_;
228     }
229     bool operator!=(const KeyIterator& other) const {
230       return iter_ != other.iter_;
231     }
232   };
233 
AddInternal(V data)234   KeyType AddInternal(V data) {
235     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
236     DCHECK(!check_on_null_data_ || data);
237     KeyType this_id = next_id_;
238     DCHECK(data_.find(this_id) == data_.end()) << "Inserting duplicate item";
239     data_[this_id] = std::move(data);
240     next_id_++;
241     return this_id;
242   }
243 
AddWithIDInternal(V data,KeyType id)244   void AddWithIDInternal(V data, KeyType id) {
245     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
246     DCHECK(!check_on_null_data_ || data);
247     if (IsRemoved(id)) {
248       removed_ids_.erase(id);
249     } else {
250       DCHECK(data_.find(id) == data_.end()) << "Inserting duplicate item";
251     }
252     data_[id] = std::move(data);
253   }
254 
IsRemoved(KeyType key)255   bool IsRemoved(KeyType key) const {
256     return removed_ids_.find(key) != removed_ids_.end();
257   }
258 
Compact()259   void Compact() {
260     DCHECK_EQ(0, iteration_depth_);
261     for (const auto& i : removed_ids_)
262       data_.erase(i);
263     removed_ids_.clear();
264   }
265 
266   // Keep track of how many iterators are currently iterating on us to safely
267   // handle removing items during iteration.
268   int iteration_depth_;
269 
270   // Keep set of IDs that should be removed after the outermost iteration has
271   // finished. This way we manage to not invalidate the iterator when an element
272   // is removed.
273   base::flat_set<KeyType> removed_ids_;
274 
275   // The next ID that we will return from Add()
276   KeyType next_id_;
277 
278   HashTable data_;
279 
280   // See description above setter.
281   bool check_on_null_data_;
282 
283   SEQUENCE_CHECKER(sequence_checker_);
284 
285   DISALLOW_COPY_AND_ASSIGN(IDMap);
286 };
287 
288 }  // namespace base
289 
290 #endif  // BASE_CONTAINERS_ID_MAP_H_
291