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 | |
9 | namespace mbgl { |
10 | namespace style { |
11 | namespace 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 | |
80 | struct Error { std::string message; }; |
81 | |
82 | template <typename T> |
83 | class ConversionTraits; |
84 | |
85 | class Convertible { |
86 | public: |
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 | |
188 | private: |
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 | |
296 | template <class T, class Enable = void> |
297 | struct Converter; |
298 | |
299 | template <class T, class...Args> |
300 | optional<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 | |