1#pragma once
2
3#include <mbgl/util/optional.hpp>
4#include <mbgl/util/feature.hpp>
5#include <mbgl/util/geojson.hpp>
6
7#include <string>
8
9namespace mbgl {
10namespace style {
11namespace conversion {
12
13/*
14 The `conversion` namespace defines conversions from JSON structures conforming to the schema defined by
15 the Mapbox Style Specification, to the various C++ types that form the C++ model of that domain:
16
17 * `std::unique_ptr<Source>`
18 * `std::unique_ptr<Layer>`
19 * `Filter`
20 * `PropertyValue<T>`
21
22 A single template function serves as the public interface:
23
24 template <class T>
25 optional<T> convert(const Convertible& input, Error& error);
26
27 Where `T` is one of the above types. If the conversion fails, the result is empty, and the
28 error parameter includes diagnostic text suitable for presentation to a library user. Otherwise,
29 a filled optional is returned.
30
31 `Convertible` is a type that encapsulates a special form of polymorphism over various underlying types that
32 can serve as input to the conversion algorithm. For instance, on macOS, we need to support
33 conversion from both RapidJSON types, and a JSON structure represented with `NSArray`/`NSDictionary`/etc.
34 On Qt, we need to support conversion from RapidJSON types and QVariant.
35
36 We don't want to use traditional forms of polymorphism to accomplish this:
37
38 * Compile time polymorphism using a template parameter for the actual value type leads to
39 excessive code bloat and long compile times.
40 * Runtime polymorphism using virtual methods requires extra heap allocation and ubiquitous
41 use of std::unique_ptr, unsuitable for this performance-sensitive code.
42
43 Therefore, we're using a custom implementation of runtime polymorphism where we manually create and
44 dispatch through a table of function pointers (vtable), while keeping the storage for any of the possible
45 underlying types inline on the stack, using `std::aligned_storage`.
46
47 For a given underlying type T, an explicit specialization of `ConversionTraits<T>` must be provided. This
48 specialization must provide the following static methods:
49
50 * `isUndefined(v)` -- returns a boolean indication whether `v` is undefined or a JSON null
51
52 * `isArray(v)` -- returns a boolean indicating whether `v` represents a JSON array
53 * `arrayLength(v)` -- called only if `isArray(v)`; returns a size_t length
54 * `arrayMember(v)` -- called only if `isArray(v)`; returns `V` or `V&`
55
56 * `isObject(v)` -- returns a boolean indicating whether `v` represents a JSON object
57 * `objectMember(v, name)` -- called only if `isObject(v)`; `name` is `const char *`; return value:
58 * is true when evaluated in a boolean context iff the named member exists
59 * is convertable to a `V` or `V&` when dereferenced
60 * `eachMember(v, [] (const std::string&, const V&) -> optional<Error> {...})` -- called
61 only if `isObject(v)`; calls the provided lambda once for each key and value of the object;
62 short-circuits if any call returns an `Error`
63
64 * `toBool(v)` -- returns `optional<bool>`, absence indicating `v` is not a JSON boolean
65 * `toNumber(v)` -- returns `optional<float>`, absence indicating `v` is not a JSON number
66 * `toDouble(v)` -- returns `optional<double>`, absence indicating `v` is not a JSON number
67 * `toString(v)` -- returns `optional<std::string>`, absence indicating `v` is not a JSON string
68 * `toValue(v)` -- returns `optional<Value>`, a variant type, for generic conversion,
69 absence indicating `v` is not a boolean, number, or string. Numbers should be converted to
70 unsigned integer, signed integer, or floating point, in descending preference.
71
72 In addition, the type T must be move-constructable. And finally, `Convertible::Storage`, a typedef for
73 `std::aligned_storage_t`, must be large enough to satisfy the memory requirements for any of the
74 possible underlying types. (A static assert will fail if this is not the case.)
75
76 `Convertible` itself is movable, but not copyable. A moved-from `Convertible` is in an invalid state;
77 you must not do anything with it except let it go out of scope.
78*/
79
80struct Error { std::string message; };
81
82template <typename T>
83class ConversionTraits;
84
85class Convertible {
86public:
87 template <typename T>
88 Convertible(T&& value) : vtable(vtableForType<std::decay_t<T>>()) {
89 static_assert(sizeof(Storage) >= sizeof(std::decay_t<T>), "Storage must be large enough to hold value type");
90 new (static_cast<void*>(&storage)) std::decay_t<T>(std::forward<T>(value));
91 }
92
93 Convertible(Convertible&& v)
94 : vtable(v.vtable)
95 {
96 if (vtable) {
97 vtable->move(std::move(v.storage), this->storage);
98 }
99 }
100
101 ~Convertible() {
102 if (vtable) {
103 vtable->destroy(storage);
104 }
105 }
106
107 Convertible& operator=(Convertible&& v) {
108 if (vtable) {
109 vtable->destroy(storage);
110 }
111 vtable = v.vtable;
112 if (vtable) {
113 vtable->move(std::move(v.storage), this->storage);
114 }
115 v.vtable = nullptr;
116 return *this;
117 }
118
119 Convertible() = delete;
120 Convertible(const Convertible&) = delete;
121 Convertible& operator=(const Convertible&) = delete;
122
123 friend inline bool isUndefined(const Convertible& v) {
124 assert(v.vtable);
125 return v.vtable->isUndefined(v.storage);
126 }
127
128 friend inline bool isArray(const Convertible& v) {
129 assert(v.vtable);
130 return v.vtable->isArray(v.storage);
131 }
132
133 friend inline std::size_t arrayLength(const Convertible& v) {
134 assert(v.vtable);
135 return v.vtable->arrayLength(v.storage);
136 }
137
138 friend inline Convertible arrayMember(const Convertible& v, std::size_t i) {
139 assert(v.vtable);
140 return v.vtable->arrayMember(v.storage, i);
141 }
142
143 friend inline bool isObject(const Convertible& v) {
144 assert(v.vtable);
145 return v.vtable->isObject(v.storage);
146 }
147
148 friend inline optional<Convertible> objectMember(const Convertible& v, const char * name) {
149 assert(v.vtable);
150 return v.vtable->objectMember(v.storage, name);
151 }
152
153 friend inline optional<Error> eachMember(const Convertible& v, const std::function<optional<Error> (const std::string&, const Convertible&)>& fn) {
154 assert(v.vtable);
155 return v.vtable->eachMember(v.storage, fn);
156 }
157
158 friend inline optional<bool> toBool(const Convertible& v) {
159 assert(v.vtable);
160 return v.vtable->toBool(v.storage);
161 }
162
163 friend inline optional<float> toNumber(const Convertible& v) {
164 assert(v.vtable);
165 return v.vtable->toNumber(v.storage);
166 }
167
168 friend inline optional<double> toDouble(const Convertible& v) {
169 assert(v.vtable);
170 return v.vtable->toDouble(v.storage);
171 }
172
173 friend inline optional<std::string> toString(const Convertible& v) {
174 assert(v.vtable);
175 return v.vtable->toString(v.storage);
176 }
177
178 friend inline optional<Value> toValue(const Convertible& v) {
179 assert(v.vtable);
180 return v.vtable->toValue(v.storage);
181 }
182
183 friend inline optional<GeoJSON> toGeoJSON(const Convertible& v, Error& error) {
184 assert(v.vtable);
185 return v.vtable->toGeoJSON(v.storage, error);
186 }
187
188private:
189#if __ANDROID__
190 // Android: JSValue* or mbgl::android::Value
191 using Storage = std::aligned_storage_t<32, 8>;
192#elif __QT__
193 // Qt: JSValue* or QVariant
194 using Storage = std::aligned_storage_t<32, 8>;
195#else
196 // Node: JSValue* or v8::Local<v8::Value>
197 // iOS/macOS: JSValue* or id
198 using Storage = std::aligned_storage_t<8, 8>;
199#endif
200
201 struct VTable {
202 void (*move) (Storage&& src, Storage& dest);
203 void (*destroy) (Storage&);
204
205 bool (*isUndefined) (const Storage&);
206
207 bool (*isArray) (const Storage&);
208 std::size_t (*arrayLength) (const Storage&);
209 Convertible (*arrayMember) (const Storage&, std::size_t);
210
211 bool (*isObject) (const Storage&);
212 optional<Convertible> (*objectMember) (const Storage&, const char *);
213 optional<Error> (*eachMember) (const Storage&, const std::function<optional<Error> (const std::string&, const Convertible&)>&);
214
215 optional<bool> (*toBool) (const Storage&);
216 optional<float> (*toNumber) (const Storage&);
217 optional<double> (*toDouble) (const Storage&);
218 optional<std::string> (*toString) (const Storage&);
219 optional<Value> (*toValue) (const Storage&);
220
221 // https://github.com/mapbox/mapbox-gl-native/issues/5623
222 optional<GeoJSON> (*toGeoJSON) (const Storage&, Error&);
223 };
224
225 // Extracted this function from the table below to work around a GCC bug with differing
226 // visibility settings for capturing lambdas: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80947
227 template <typename T>
228 static auto vtableEachMember(const Storage& s, const std::function<optional<Error>(const std::string&, const Convertible&)>& fn) {
229 return ConversionTraits<T>::eachMember(reinterpret_cast<const T&>(s), [&](const std::string& k, T&& v) {
230 return fn(k, Convertible(std::move(v)));
231 });
232 }
233
234 template <typename T>
235 static VTable* vtableForType() {
236 using Traits = ConversionTraits<T>;
237 static VTable vtable = {
238 [] (Storage&& src, Storage& dest) {
239 auto srcValue = reinterpret_cast<T&&>(src);
240 new (static_cast<void*>(&dest)) T(std::move(srcValue));
241 srcValue.~T();
242 },
243 [] (Storage& s) {
244 reinterpret_cast<T&>(s).~T();
245 },
246 [] (const Storage& s) {
247 return Traits::isUndefined(reinterpret_cast<const T&>(s));
248 },
249 [] (const Storage& s) {
250 return Traits::isArray(reinterpret_cast<const T&>(s));
251 },
252 [] (const Storage& s) {
253 return Traits::arrayLength(reinterpret_cast<const T&>(s));
254 },
255 [] (const Storage& s, std::size_t i) {
256 return Convertible(Traits::arrayMember(reinterpret_cast<const T&>(s), i));
257 },
258 [] (const Storage& s) {
259 return Traits::isObject(reinterpret_cast<const T&>(s));
260 },
261 [] (const Storage& s, const char * key) {
262 optional<T> member = Traits::objectMember(reinterpret_cast<const T&>(s), key);
263 if (member) {
264 return optional<Convertible>(Convertible(std::move(*member)));
265 } else {
266 return optional<Convertible>();
267 }
268 },
269 vtableEachMember<T>,
270 [] (const Storage& s) {
271 return Traits::toBool(reinterpret_cast<const T&>(s));
272 },
273 [] (const Storage& s) {
274 return Traits::toNumber(reinterpret_cast<const T&>(s));
275 },
276 [] (const Storage& s) {
277 return Traits::toDouble(reinterpret_cast<const T&>(s));
278 },
279 [] (const Storage& s) {
280 return Traits::toString(reinterpret_cast<const T&>(s));
281 },
282 [] (const Storage& s) {
283 return Traits::toValue(reinterpret_cast<const T&>(s));
284 },
285 [] (const Storage& s, Error& err) {
286 return Traits::toGeoJSON(reinterpret_cast<const T&>(s), err);
287 }
288 };
289 return &vtable;
290 }
291
292 VTable* vtable;
293 Storage storage;
294};
295
296template <class T, class Enable = void>
297struct Converter;
298
299template <class T, class...Args>
300optional<T> convert(const Convertible& value, Error& error, Args&&...args) {
301 return Converter<T>()(value, error, std::forward<Args>(args)...);
302}
303
304} // namespace conversion
305} // namespace style
306} // namespace mbgl
307

source code of qtlocation/src/3rdparty/mapbox-gl-native/include/mbgl/style/conversion.hpp