1 /*
2 * Copyright (c) 2022, Alliance for Open Media. All rights reserved
3 *
4 * This source code is subject to the terms of the BSD 3-Clause Clear License
5 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
6 * License was not distributed with this source code in the LICENSE file, you
7 * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
8 * Alliance for Open Media Patent License 1.0 was not distributed with this
9 * source code in the PATENTS file, you can obtain it at
10 * www.aomedia.org/license/patent.
11 */
12 #ifndef COMMON_UTILS_MAP_UTILS_H_
13 #define COMMON_UTILS_MAP_UTILS_H_
14
15 #include "absl/base/no_destructor.h"
16 #include "absl/container/flat_hash_map.h"
17 #include "absl/functional/function_ref.h"
18 #include "absl/log/check.h"
19 #include "absl/log/log.h"
20 #include "absl/status/status.h"
21 #include "absl/status/statusor.h"
22 #include "absl/strings/str_cat.h"
23 #include "absl/strings/string_view.h"
24
25 namespace iamf_tools {
26
27 /*!\brief Looks up a key in a map and returns a status or value.
28 *
29 * When lookup fails the error message will contain the `context` string
30 * followed by "= $KEY", where $KEY is the stringified `key`.
31 *
32 * Some mapping have sufficient context in the typenames, for example:
33 * - Input Map: A map of `PersonName` to `Birthday`.
34 * - Typename-based context: "`Birthday` for `PersonName`".
35 * - Output message: "`Birthday` for `PersonName`= John was not found in the
36 * map.".
37 *
38 * Some mappings provide insufficient context in the typenames. Or the typenames
39 * would be easily confused. Variable names or phrases should be used as
40 * context:
41 * - Input Map: A map of `absl::string_view` names to `int` ages.
42 * - Variable-based context: "`age` for `name`".
43 * - Phrase-based context: or "Age for name".
44 * Or:
45 * - Input Map: A map of `proto::Type` to `iamf_tools::Type`.
46 * - Phrase-based context: "Internal version of proto `Type`".
47 *
48 * \param map Map to search.
49 * \param key Key to search for.
50 * \param context Context to insert into the error message for debugging
51 * purposes.
52 * \return Associated value if lookup is successful. `absl::NotFoundError()`
53 * when lookup fails.
54 */
55 template <typename Key, typename Value>
LookupInMap(const absl::flat_hash_map<Key,Value> & map,const Key & key,absl::string_view context)56 absl::StatusOr<Value> LookupInMap(const absl::flat_hash_map<Key, Value>& map,
57 const Key& key, absl::string_view context) {
58 auto iter = map.find(key);
59 if (iter != map.end()) [[likely]] {
60 return iter->second;
61 }
62
63 return absl::NotFoundError(absl::StrCat(
64 context, "= ", key, " was not found in the map.",
65 map.empty() ? " The map is empty. Did initialization fail?" : ""));
66 }
67
68 /*!\brief Looks up a key in a map and copies the value to the output argument.
69 *
70 * \param map Map to search.
71 * \param key Key to search for.
72 * \param context Context to insert into the error message for debugging
73 * purposes. Forwared to `LookupInMap` which has detailed documentation
74 * on usage.
75 * \param value Output argument to write the value to.
76 * \return `absl::OkStatus()` if lookup is successful. `absl::NotFoundError()`
77 * when lookup fails.
78 */
79 template <typename Key, typename Value>
CopyFromMap(const absl::flat_hash_map<Key,Value> & map,const Key & key,absl::string_view context,Value & value)80 absl::Status CopyFromMap(const absl::flat_hash_map<Key, Value>& map,
81 const Key& key, absl::string_view context,
82 Value& value) {
83 const auto& result = LookupInMap(map, key, context);
84 if (result.ok()) [[likely]] {
85 value = *result;
86 }
87 return result.status();
88 }
89
90 /*!\brief Looks up a key in a map and calls a setter with the value.
91 *
92 * \param map Map to search.
93 * \param key Key to search for.
94 * \param context Context to insert into the error message for debugging
95 * purposes. Forwared to `LookupInMap` which has detailed documentation
96 * on usage.
97 * \param setter Function to call with the found value.
98 * \return `absl::OkStatus()` if lookup is successful. `absl::NotFoundError()`
99 * when lookup fails. Or an error code forwarded from the setter.
100 */
101 template <typename Key, typename Value>
SetFromMap(const absl::flat_hash_map<Key,Value> & map,const Key & key,absl::string_view context,absl::FunctionRef<void (Value)> setter)102 absl::Status SetFromMap(const absl::flat_hash_map<Key, Value>& map,
103 const Key& key, absl::string_view context,
104 absl::FunctionRef<void(Value)> setter) {
105 const auto& result = LookupInMap(map, key, context);
106 if (result.ok()) [[likely]] {
107 setter(*result);
108 }
109 return result.status();
110 }
111
112 /*!\brief Returns a map for static storage from a container of pairs.
113 *
114 * \param pairs Container of pairs to convert to a map. The first value must be
115 * unique among all pairs.
116 * \return Map suitable for static storage. Or an empty map if the first value
117 * of a pair is not unique.
118 */
119 template <class InputContainer>
BuildStaticMapFromPairs(const InputContainer & pairs)120 auto BuildStaticMapFromPairs(const InputContainer& pairs) {
121 typedef absl::flat_hash_map<typename InputContainer::value_type::first_type,
122 typename InputContainer::value_type::second_type>
123 MapFromPairs;
124 return absl::NoDestructor<MapFromPairs>([&]() {
125 MapFromPairs map_from_pairs;
126 for (const auto& [key, value] : pairs) {
127 const auto& [unused_iter, inserted] = map_from_pairs.insert({key, value});
128 if (!inserted) [[unlikely]] {
129 LOG(ERROR) << "Failed building map from pairs. Duplicate key= "
130 << absl::StrCat(key) << ".";
131 return MapFromPairs{};
132 }
133 }
134 return map_from_pairs;
135 }());
136 }
137
138 /*!\brief Returns a map for static storage from a container of inverted pairs.
139 *
140 * \param pairs Container of pairs to invert and to convert to a map. The second
141 * value must be unique among all pairs.
142 * \return Map suitable for static storage. Or an empty map if the second value
143 * of a pair is not unique.
144 */
145 template <class InputContainer>
BuildStaticMapFromInvertedPairs(const InputContainer & pairs)146 auto BuildStaticMapFromInvertedPairs(const InputContainer& pairs) {
147 typedef absl::flat_hash_map<typename InputContainer::value_type::second_type,
148 typename InputContainer::value_type::first_type>
149 MapFromInvertedPairs;
150 return absl::NoDestructor<MapFromInvertedPairs>([&]() {
151 MapFromInvertedPairs map_from_inverted_pairs;
152 for (const auto& [value, key] : pairs) {
153 const auto& [unused_iter, inserted] =
154 map_from_inverted_pairs.insert({key, value});
155 if (!inserted) [[unlikely]] {
156 LOG(ERROR) << "Failed building map from pairs. Duplicate key= "
157 << absl::StrCat(key) << ".";
158 return MapFromInvertedPairs{};
159 }
160 }
161 return map_from_inverted_pairs;
162 }());
163 }
164
165 } // namespace iamf_tools
166
167 #endif // COMMON_UTILS_MAP_UTILS_H_
168