The open source OpenXR runtime
1// Copyright 2020-2021, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3// Author: Rylie Pavlik <rylie.pavlik@collabora.com>
4
5#pragma once
6#include <assert.h>
7#include <jni.h>
8#include <jnipp.h>
9
10namespace wrap {
11
12/*!
13 * Base class for types wrapping Java types.
14 *
15 * Derived types are encouraged to have a nested `struct Meta`, inheriting
16 * publicly from MetaBaseDroppable or MetaBase, with a singleton accessor named
17 * `data()`, and a private constructor (implemented in a .cpp file, not the
18 * header) that populates jni::method_t, jni::field_t, etc. members for each
19 * method, etc. of interest.
20 */
21class ObjectWrapperBase {
22 public:
23 /*!
24 * Default constructor.
25 */
26 ObjectWrapperBase() = default;
27
28 /*!
29 * Construct from a jni::Object.
30 */
31 explicit ObjectWrapperBase(jni::Object obj) : obj_(std::move(obj)) {}
32
33 /*!
34 * Construct from a nullptr.
35 */
36 explicit ObjectWrapperBase(std::nullptr_t const &) : obj_() {}
37
38 /*!
39 * Evaluate if this is non-null
40 */
41 explicit operator bool() const noexcept { return !obj_.isNull(); }
42
43 /*!
44 * Is this object null?
45 */
46 bool isNull() const noexcept { return obj_.isNull(); }
47
48 /*!
49 * Get the wrapped jni::Object
50 */
51 jni::Object &object() noexcept { return obj_; }
52
53 /*!
54 * Get the wrapped jni::Object (const overload)
55 */
56 jni::Object const &object() const noexcept { return obj_; }
57
58 private:
59 jni::Object obj_;
60};
61
62/*!
63 * Equality comparison for a wrapped Java object.
64 */
65static inline bool operator==(ObjectWrapperBase const &lhs,
66 ObjectWrapperBase const &rhs) noexcept {
67 return lhs.object() == rhs.object();
68}
69
70/*!
71 * Inequality comparison for a wrapped Java object.
72 */
73static inline bool operator!=(ObjectWrapperBase const &lhs,
74 ObjectWrapperBase const &rhs) noexcept {
75 return lhs.object() != rhs.object();
76}
77
78/*!
79 * Equality comparison between a wrapped Java object and @p nullptr.
80 */
81static inline bool operator==(ObjectWrapperBase const &obj,
82 std::nullptr_t) noexcept {
83 return obj.isNull();
84}
85
86/*!
87 * Equality comparison between a wrapped Java object and @p nullptr.
88 */
89static inline bool operator==(std::nullptr_t,
90 ObjectWrapperBase const &obj) noexcept {
91 return obj.isNull();
92}
93
94/*!
95 * Inequality comparison between a wrapped Java object and @p nullptr.
96 */
97static inline bool operator!=(ObjectWrapperBase const &obj,
98 std::nullptr_t) noexcept {
99 return !(obj == nullptr);
100}
101
102/*!
103 * Inequality comparison between a wrapped Java object and @p nullptr.
104 */
105static inline bool operator!=(std::nullptr_t,
106 ObjectWrapperBase const &obj) noexcept {
107 return !(obj == nullptr);
108}
109
110/*!
111 * Base class for Meta structs where you want the reference to the Class object
112 * to persist (indefinitely).
113 *
114 * Mostly for classes that would stick around anyway (e.g.
115 * @p java.lang.ClassLoader ) where many operations are on static
116 * methods/fields. Use of a non-static method or field does not require such a
117 * reference, use MetaBaseDroppable in those cases.
118 */
119class MetaBase {
120 public:
121 /*!
122 * Gets a reference to the class object.
123 *
124 * Unlike MetaBaseDroppable, here we know that the class object ref is
125 * alive.
126 */
127 jni::Class const &clazz() const noexcept { return clazz_; }
128 /*!
129 * Gets a reference to the class object.
130 *
131 * Provided here for parallel API to MetaBaseDroppable, despite being
132 * synonymous with clazz() here.
133 */
134 jni::Class const &classRef() const noexcept { return clazz_; }
135
136 /*!
137 * Get the class name, with namespaces delimited by `/`.
138 */
139 const char *className() const noexcept { return classname_; }
140
141 protected:
142 /*!
143 * Construct.
144 *
145 * @param classname The class name, fully qualified, with namespaces
146 * delimited by `/`.
147 * @param clazz The jclass object for the class in question, if known.
148 */
149 explicit MetaBase(const char *classname, jni::jclass clazz = nullptr)
150 : classname_(classname), clazz_() {
151 if (clazz != nullptr) {
152 // The 0 makes it a global ref.
153 clazz_ = jni::Class{clazz, 0};
154 } else {
155 clazz_ = jni::Class{classname};
156 }
157 }
158
159 private:
160 const char *classname_;
161 jni::Class clazz_;
162};
163
164/*!
165 * Base class for Meta structs where you don't need the reference to the Class
166 * object to persist. (This is most uses.)
167 */
168class MetaBaseDroppable {
169 public:
170 /*!
171 * Gets the class object.
172 *
173 * Works regardless of whether dropClassRef() has been called - it's just
174 * slower if it has.
175 */
176 jni::Class clazz() const {
177 if (clazz_.isNull()) {
178 return {classname_};
179 }
180 return clazz_;
181 }
182
183 /*!
184 * Get the class name, with namespaces delimited by `/`.
185 */
186 const char *className() const noexcept { return classname_; }
187
188 /*!
189 * May be called in/after the derived constructor, to drop the reference to
190 * the class object if it's no longer needed.
191 */
192 void dropClassRef() { clazz_ = jni::Class{}; }
193
194 protected:
195 /*!
196 * Construct.
197 *
198 * Once you are done constructing your derived struct, you may call
199 * dropClassRef() and still safely use non-static method and field IDs
200 * retrieved.
201 *
202 * @param classname The class name, fully qualified, with namespaces
203 * delimited by `/`.
204 * @param clazz The jclass object for the class in question, if known.
205 */
206 explicit MetaBaseDroppable(const char *classname,
207 jni::jclass clazz = nullptr)
208 : classname_(classname), clazz_() {
209 if (clazz != nullptr) {
210 // The 0 makes it a global ref.
211 clazz_ = jni::Class{clazz, 0};
212 } else {
213 clazz_ = jni::Class{classname};
214 }
215 }
216
217 /*!
218 * Gets a reference to the class object, but is non-null only if it's still
219 * cached.
220 *
221 * Only for used in derived constructors/initializers, where you know you
222 * haven't dropped this yet.
223 */
224 jni::Class const &classRef() const { return clazz_; }
225
226 private:
227 const char *classname_;
228 jni::Class clazz_;
229};
230
231/*!
232 * Implementation namespace for these JNI wrappers.
233 *
234 * They can be ignored if you aren't adding/extending wrappers.
235 */
236namespace impl {
237/*!
238 * Type-aware wrapper for a field ID.
239 *
240 * This is a smarter alternative to just using jni::field_t since it avoids
241 * having to repeat the type in the accessor, without using any more storage.
242 *
243 * @see StaticFieldId for the equivalent for static fields.
244 * @see WrappedFieldId for the equivalent for structures that we wrap.
245 */
246template <typename T> struct FieldId {
247 public:
248 FieldId(jni::Class const &clazz, const char *name)
249 : id(clazz.getField<T>(name)) {}
250
251 const jni::field_t id;
252};
253
254/*!
255 * Get the value of field @p field in Java object @p obj.
256 *
257 * This is found by argument-dependent lookup and can be used unqualified.
258 *
259 * @relates FieldId
260 */
261template <typename T>
262static inline T get(FieldId<T> const &field, jni::Object const &obj) {
263 assert(!obj.isNull());
264 return obj.get<T>(field.id);
265}
266/*!
267 * Type-aware wrapper for a static field ID.
268 *
269 * This is a smarter alternative to just using jni::field_t since it avoids
270 * having to repeat the type in the accessor, without using any more storage.
271 *
272 * @see FieldId
273 */
274template <typename T> struct StaticFieldId {
275 public:
276 StaticFieldId(jni::Class const &clazz, const char *name)
277 : id(clazz.getStaticField<T>(name)) {}
278
279 const jni::field_t id;
280};
281
282/*!
283 * Get the value of static field @p field in Java type @p clazz.
284 *
285 * This is found by argument-dependent lookup and can be used unqualified.
286 *
287 * @relates FieldId
288 */
289template <typename T>
290static inline T get(StaticFieldId<T> const &field, jni::Class const &clazz) {
291 assert(!clazz.isNull());
292 return clazz.get<T>(field.id);
293}
294
295/*!
296 * Type-aware wrapper for a field ID of a wrapped structure type.
297 *
298 * This is a smarter alternative to just using jni::field_t since it avoids
299 * having to repeat the type in the accessor, without using any more storage.
300 *
301 * Requires that the structure wrapper provides
302 * `static constexpr const char *getTypeName() noexcept;`
303 *
304 * @see FieldId
305 */
306template <typename T> struct WrappedFieldId {
307 public:
308 WrappedFieldId(jni::Class const &clazz, const char *name)
309 : id(lookupField(clazz, name)) {}
310
311 const jni::field_t id;
312
313 private:
314 /*!
315 * Helper for field ID lookup, mostly to avoid calling c_str() on a string
316 * temporary.
317 */
318 static jni::field_t lookupField(jni::Class const &clazz, const char *name) {
319 std::string fullType = std::string("L") + T::getTypeName() + ";";
320 return clazz.getField(name, fullType.c_str());
321 }
322};
323
324/*!
325 * Get the value of field @p field in Java object @p obj.
326 *
327 * This is found by argument-dependent lookup and can be used unqualified.
328 *
329 * @relates WrappedFieldId
330 */
331template <typename T>
332static inline T get(WrappedFieldId<T> const &field, jni::Object const &obj) {
333 assert(!obj.isNull());
334 return T{obj.get<jni::Object>(field.id)};
335}
336} // namespace impl
337} // namespace wrap