xref: /aosp_15_r20/external/fbjni/test/jni/doc_tests.cpp (revision 65c59e023c5336bbd4a23be7af78407e3d80e7e7)
1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <cassert>
18 #include <cstring>
19 #include <stdexcept>
20 #include <type_traits>
21 #include <vector>
22 
23 // SECTION registration
24 #include <fbjni/fbjni.h>
25 using namespace facebook::jni;
26 // END
27 
28 // SECTION byte_buffer
29 #include <fbjni/ByteBuffer.h>
30 // END
31 
32 // We can put all of our code in an anonymous namespace if
33 // it is not used from any other C++ code.
34 namespace {
35 
36 // SECTION inheritance
37 struct JMyBaseClass : JavaClass<JMyBaseClass> {
38   static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/MyBaseClass;";
39 };
40 
41 struct JMyDerivedClass : JavaClass<JMyDerivedClass, JMyBaseClass> {
42   static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/MyDerivedClass;";
43 };
44 
45 /* MARKDOWN
46 This will allow implicit casts from Derived to Base and explicit downcasts.
47 When no base class is given, JObject will be used as the base.
48 // END
49 */
50 
51 // SECTION nested_class
52 struct JNested : JavaClass<JNested> {
53   static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/Outer$Nested;";
create__anon99d09a770111::JNested54   static local_ref<JNested> create() {
55     return newInstance();
56   }
57 };
58 // END
59 
60 // SECTION constructor
61 struct JDataHolder : JavaClass<JDataHolder> {
62   static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/DataHolder;";
63   // newInstance should be wrapped to ensure compile-time checking of call
64   // sites.
create__anon99d09a770111::JDataHolder65   static local_ref<JDataHolder> create(int i, std::string const& s) {
66     // Constructor is looked up by argument types at *runtime*.
67     return newInstance(i, s);
68   }
69   // END
70 
71   // SECTION fields
getAndSetFields__anon99d09a770111::JDataHolder72   void getAndSetFields() {
73     static const auto cls = javaClassStatic();
74     // Primitive fields.
75     static const auto iField = cls->getField<jint>("i");
76     jint i = this->getFieldValue(iField);
77     this->setFieldValue(iField, i + 1);
78     // Object fields work for standard classes and your own JavaObject classes.
79     static const auto sField = cls->getField<JString>("s");
80     // Object are returned as local refs ...
81     local_ref<JString> s = this->getFieldValue(sField);
82     // and can be set from any ref.
83     this->setFieldValue(sField, make_jstring(s->toStdString() + "1").get());
84     // Static fields work the same, but getStaticField, getStaticFieldValue,
85     // and setStaticFieldValue must all be called on the class object.
86     static const auto someInstanceField =
87         cls->getStaticField<JDataHolder>("someInstance");
88     auto inst = cls->getStaticFieldValue(someInstanceField);
89     if (!inst) {
90       // NOTE: Can't use cls here because it is declared const.
91       getClass()->setStaticFieldValue(someInstanceField, self());
92     }
93   }
94   // END
95 
getStr__anon99d09a770111::JDataHolder96   local_ref<JString> getStr() {
97     static const auto cls = javaClassStatic();
98     static const auto sField = cls->getField<JString>("s");
99     return getFieldValue(sField);
100   }
101 };
102 
103 // SECTION registration
104 // Standard declaration for a normal class (no C++ fields).
105 struct DocTests : JavaClass<DocTests> {
106   static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/DocTests;";
107   // END
108 
109   // SECTION constructor
110   // Call-site in another file.
runConstructor__anon99d09a770111::DocTests111   static local_ref<JDataHolder> runConstructor(alias_ref<JClass> clazz) {
112     // Call to ordinatry C++ function is checked at *compile time*.
113     return JDataHolder::create(1, "hi");
114   }
115   // END
116 
117   // SECTION basic_methods
118  public:
119   // Java methods should usually be wrapped by C++ methods for ease-of-use.
120   // (Most other examples in this document will inline these for brevity.)
callVoidMethod__anon99d09a770111::DocTests121   void callVoidMethod() {
122     static const auto method = getClass()->getMethod<void()>("voidMethod");
123     // self() returns the raw JNI reference to this object.
124     method(self());
125   }
callStaticVoidMethod__anon99d09a770111::DocTests126   static void callStaticVoidMethod() {
127     static const auto cls = javaClassStatic();
128     static const auto method = cls->getStaticMethod<void()>("staticVoidMethod");
129     method(cls);
130   }
131 
132   // Native implementations of Java methods can be private.
133  private:
134   // For non-Hybrid objects, all JNI methods must be static on the C++ side
135   // because only Hybrid objects can have C++ state.
nativeVoidMethod__anon99d09a770111::DocTests136   static void nativeVoidMethod(
137       // All non-static methods receive "this" as a first argument.
138       alias_ref<DocTests> thiz) {
139     // Make sure we got the right object.
140     assert(thiz->toString() == "instance of DocTests");
141     thiz->callVoidMethod();
142   }
staticNativeVoidMethod__anon99d09a770111::DocTests143   static void staticNativeVoidMethod(
144       // All static methods receive the class as a first argument.
145       alias_ref<JClass> clazz) {
146     assert(clazz->toString() == "class com.facebook.jni.DocTests");
147     DocTests::callStaticVoidMethod();
148   }
149   // END
150 
151   // SECTION primitives
152   static jlong
addSomeNumbers__anon99d09a770111::DocTests153   addSomeNumbers(alias_ref<JClass> clazz, jbyte b, jshort s, jint i) {
154     static const auto doubler = clazz->getStaticMethod<jlong(jint)>("doubler");
155     jlong l = doubler(clazz, 4);
156     return b + s + i + l;
157   }
158 
159   /* MARKDOWN
160   Argument and return types can be in either JNI style or C++ style.
161 
162   | Java type | JNI types |
163   | --- | --- |
164   | `boolean` | `jboolean`, `bool` |
165   | `byte` | `jbyte`, `int8_t` |
166   | `char` | `jchar` |
167   | `short` | `jshort`, `short`, `int16_t` |
168   | `int` | `jint`, `int`, `int32_t` |
169   | `long` | `jlong`, `int64_t` |
170   | `float` | `jfloat`, `float` |
171   | `double` | `jdouble`, `double` |
172   // END
173   */
174 
175   // SECTION strings
fancyCat__anon99d09a770111::DocTests176   static std::string fancyCat(
177       alias_ref<JClass> clazz,
178       // Native methods can receive strings as JString (direct JNI reference)
179       // ...
180       alias_ref<JString> s1,
181       // or as std::string (converted to real UTF-8).
182       std::string s2) {
183     // Convert JString to std::string.
184     std::string result = s1->toStdString();
185     // Java methods can receive and return JString ...
186     static const auto doubler_java =
187         clazz->getStaticMethod<JString(JString)>("doubler");
188     result += doubler_java(clazz, *s1)->toStdString();
189     // and also std::string (converted from real UTF-8).
190     static const auto doubler_std =
191         clazz->getStaticMethod<std::string(std::string)>("doubler");
192     result += doubler_std(clazz, s2)->toStdString();
193     // They can also receive const char*, but not return it.
194     static const auto doubler_char =
195         clazz->getStaticMethod<std::string(const char*)>("doubler");
196     result += doubler_char(clazz, s2.c_str())->toStdString();
197     // All 3 formats can be returned (std::string shown here, const char*
198     // below).
199     return result;
200   }
201 
getCString__anon99d09a770111::DocTests202   static const char* getCString(alias_ref<JClass>) {
203     // This string is converted to JString *after* getCString returns.
204     // Watch your memory lifetimes.
205     return "Watch your memory.";
206   }
207   // END
208 
209   // SECTION primitive_arrays
primitiveArrays__anon99d09a770111::DocTests210   static local_ref<JArrayInt> primitiveArrays(
211       alias_ref<JClass> clazz,
212       // JArrayX is available for all primitives.
213       alias_ref<JArrayInt> arr) {
214     size_t size = arr->size();
215     std::vector<jint> buffer(size + 1L);
216     // Copy elements into native memory.
217     arr->getRegion(0, size, buffer.data());
218     // Copy elements into fresh memory (returns unique_ptr<int[]>).
219     auto elements = arr->getRegion(0, size);
220     // Pin can eliminate the need for a copy.
221     {
222       auto pin = arr->pin();
223       for (size_t i = 0; i < pin.size(); i++) {
224         // Can read and/or write pin[i].
225         buffer[size] += pin[i];
226       }
227     }
228     // Allocating a new array and copying data in.
229     // (Data can also be assigned by writing to a pin.)
230     auto ret = JArrayInt::newArray(size + 1);
231     ret->setRegion(0, size + 1, buffer.data());
232     return ret;
233   }
234   // END
235 
236   // SECTION class_arrays
classArrays__anon99d09a770111::DocTests237   static local_ref<JArrayClass<JString>> classArrays(
238       alias_ref<JClass> clazz,
239       alias_ref<JArrayClass<JDataHolder>> arr) {
240     size_t size = arr->size();
241     local_ref<JArrayClass<JString>> ret = JArrayClass<JString>::newArray(size);
242     for (int i = 0; i < size; ++i) {
243       local_ref<JString> str = arr->getElement(i)->getStr();
244       ret->setElement(i, *str);
245     }
246     return ret;
247   }
248   // END
249 
250   // SECTION references
251   /* MARKDOWN
252 
253   ### `alias_ref<JFoo>`
254   `alias_ref` is a non-owning reference, like a bare pointer.
255   It is used almost exclusively for function arguments.
256 
257   ### `local_ref<JFoo>`
258   `local_ref` is a ref-counted thread-specific pointer that is invalidated upon
259   returning to Java. For variables used within a function, use `local_ref`. Most
260   functions should return `local_ref` (and let the caller convert to a
261   `global_ref` if necessary).
262 
263   ### `global_ref<JFoo>`
264   `global_ref` is a ref-counted pointer.
265   Use this for storing a reference to a Java object that may
266   outlive the current call from Java into C++
267   (e.g. class member fields are usually global refs).
268   You can create a new `global_ref` (from an `alias_ref`/`local_ref`) by calling
269   `make_global`.
270   // END
271   */
272   // SECTION references
convertReferences__anon99d09a770111::DocTests273   static local_ref<JObject> convertReferences(
274       alias_ref<JClass> clazz,
275       alias_ref<JMyDerivedClass> derived) {
276     local_ref<JMyDerivedClass> local_derived = make_local(derived);
277     global_ref<JMyDerivedClass> global_derived = make_global(derived);
278     // Store global_derived somewhere.
279     return local_derived;
280   }
281   // END
282 
283   // SECTION inheritance
castReferences__anon99d09a770111::DocTests284   static void castReferences(
285       alias_ref<JClass> clazz,
286       alias_ref<JMyBaseClass> base) {
287     // Just like raw pointers, upcasting is implicit.
288     alias_ref<JObject> obj = base;
289     // static_ref_cast is like C++ static_cast.  No runtime checking is done.
290     alias_ref<JMyDerivedClass> derived_1 =
291         static_ref_cast<JMyDerivedClass>(base);
292     // dynamic_ref_cast is like C++ dynamic_cast.
293     // It will check that the runtime Java type is actually derived from the
294     // target type.
295     try {
296       alias_ref<JMyDerivedClass> derived_2 =
297           dynamic_ref_cast<JMyDerivedClass>(base);
298       (void)derived_2;
299     } catch (const JniException& exn) {
300       // Throws ClassCastException if the cast fails.
301       throw;
302     }
303     // END
304     // Supress warnings.
305     (void)obj;
306     (void)derived_1;
307   }
308 
callGetAndSetFields__anon99d09a770111::DocTests309   static void callGetAndSetFields(
310       alias_ref<JClass> clazz,
311       alias_ref<JDataHolder> data) {
312     data->getAndSetFields();
313   }
314 
315   // SECTION jobject_jclass
showJObject__anon99d09a770111::DocTests316   static std::string showJObject(
317       alias_ref<JClass> clazz,
318       // JObject is the base class of all fbjni types.  It corresponds to
319       // java.lang.Object.
320       alias_ref<JObject> obj,
321       alias_ref<JDataHolder> data) {
322     local_ref<JClass> objClass = obj->getClass();
323     local_ref<JClass> dataClass = data->getClass();
324     local_ref<JClass> parent = dataClass->getSuperclass();
325     assert(isSameObject(parent, objClass));
326     assert(data->isInstanceOf(parent));
327     assert(objClass->isAssignableFrom(clazz));
328     std::string str = "data=";
329     {
330       // Acquires the object lock until this object goes out of scope.
331       auto lock = data->lock();
332       // Calls Object.toString and converts to std::string.
333       str += data->toString();
334     }
335     // All JavaClass types have a `javaobject` typedef, which is their raw JNI
336     // type.
337     static_assert(std::is_same<JObject::javaobject, jobject>::value, "");
338     static_assert(std::is_same<JClass::javaobject, jclass>::value, "");
339     static_assert(!std::is_same<JDataHolder::javaobject, jobject>::value, "");
340     static_assert(
341         std::is_convertible<JDataHolder::javaobject, jobject>::value, "");
342     return str;
343   }
344   // END
345 
346   // SECTION simple_exceptions
catchAndThrow__anon99d09a770111::DocTests347   static void catchAndThrow(alias_ref<JClass> clazz) {
348     try {
349       clazz->getStaticMethod<void()>("doesNotExist");
350       assert(!"Exception wasn't thrown.");
351     } catch (JniException& exn) {
352       // JniException extends std::exception, so "catch (std::exception& exn)"
353       // also works.
354       local_ref<JThrowable> underlying = exn.getThrowable();
355       const char* msg = exn.what();
356       // Throwing exceptions from C++ is fine.
357       // They will be translated to an appropriate Java exception type.
358       throw std::runtime_error(std::string() + "Caught '" + msg + "'");
359     }
360   }
361   // END
362 
363   // SECTION boxed
scaleUp__anon99d09a770111::DocTests364   static local_ref<JDouble> scaleUp(
365       alias_ref<JClass> clazz,
366       alias_ref<JInteger> number) {
367     // Boxed types exist for all Java primitive types.
368     // Unbox with ->value() or ->intValue.
369     jint unboxed = number->value();
370     jdouble scaled = unboxed * 1.5;
371     // Box with autobox() or JDouble::valueOf.
372     local_ref<JDouble> ret = autobox(scaled);
373     return ret;
374   }
375   // END
376 
377   // SECTION iterables
concatMatches__anon99d09a770111::DocTests378   static std::string concatMatches(
379       alias_ref<JClass> clazz,
380       // Note that generic types are *not* checked against Java declarations.
381       alias_ref<JList<JInteger>> values,
382       alias_ref<JMap<JString, JInteger>> names) {
383     int sum = 0;
384     std::string ret;
385     // Iterator and Iterable support C++ iteration.
386     // Collection, List, and Set support iteration and ->size().
387     for (const auto& elem : *values) {
388       sum += elem->value();
389     }
390     // Maps iterate like C++ maps.
391     for (const auto& entry : *names) {
392       if (entry.second->value() == sum) {
393         ret += entry.first->toStdString();
394       }
395     }
396     // This works if you build with C++17.
397     // for (const auto& [key, value] : *names) {
398     return ret;
399   }
400   // END
401 
402   // SECTION collections
buildCollections__anon99d09a770111::DocTests403   static local_ref<JMap<JString, JList<JInteger>>> buildCollections(
404       alias_ref<JClass> clazz) {
405     auto primes = JArrayList<JInteger>::create();
406     primes->add(autobox(2));
407     primes->add(autobox(3));
408     auto wrapper = JHashMap<JString, JList<JInteger>>::create();
409     wrapper->put(make_jstring("primes"), primes);
410     return wrapper;
411   }
412   // END
413 
414   // SECTION byte_buffer
transformBuffer__anon99d09a770111::DocTests415   static local_ref<JByteBuffer> transformBuffer(
416       alias_ref<JClass> clazz,
417       alias_ref<JByteBuffer> data) {
418     // Direct ByteBuffers are an efficient way to transfer bulk data between
419     // Java and C++.
420     if (!data->isDirect()) {
421       throw std::runtime_error("Argument is not a direct buffer.");
422     }
423     // Transform data into a local buffer.
424     std::vector<uint8_t> buffer(data->getDirectSize());
425     uint8_t* raw_data = data->getDirectBytes();
426     for (size_t i = 0; i < buffer.size(); ++i) {
427       buffer[i] = raw_data[i] + 1;
428     }
429     // Wrap our data in a buffer and pass to Java.
430     // Note that the buffer *directly* references our memory.
431     local_ref<JByteBuffer> wrapper =
432         JByteBuffer::wrapBytes(buffer.data(), buffer.size());
433     static const auto receiver =
434         clazz->getStaticMethod<void(alias_ref<JByteBuffer>)>("receiveBuffer");
435     receiver(clazz, wrapper);
436     // We can create a new buffer that owns its own memory and safely return it.
437     local_ref<JByteBuffer> ret = JByteBuffer::allocateDirect(buffer.size());
438     std::memcpy(ret->getDirectBytes(), buffer.data(), buffer.size());
439     return ret;
440   }
441   // END
442 
443  public:
444   // SECTION registration
445   // NOTE: The name of this method doesn't matter.
registerNatives__anon99d09a770111::DocTests446   static void registerNatives() {
447     javaClassStatic()->registerNatives({
448         makeNativeMethod("nativeVoidMethod", DocTests::nativeVoidMethod),
449         makeNativeMethod(
450             "staticNativeVoidMethod", DocTests::staticNativeVoidMethod),
451         // END
452         makeNativeMethod("addSomeNumbers", DocTests::addSomeNumbers),
453         makeNativeMethod("fancyCat", DocTests::fancyCat),
454         makeNativeMethod("getCString", DocTests::getCString),
455         makeNativeMethod("primitiveArrays", DocTests::primitiveArrays),
456         makeNativeMethod("convertReferences", DocTests::convertReferences),
457         makeNativeMethod("castReferences", DocTests::castReferences),
458         makeNativeMethod("runConstructor", DocTests::runConstructor),
459         makeNativeMethod("callGetAndSetFields", DocTests::callGetAndSetFields),
460         makeNativeMethod("showJObject", DocTests::showJObject),
461         makeNativeMethod("catchAndThrow", DocTests::catchAndThrow),
462         makeNativeMethod("scaleUp", DocTests::scaleUp),
463         makeNativeMethod("concatMatches", DocTests::concatMatches),
464         makeNativeMethod("buildCollections", DocTests::buildCollections),
465         makeNativeMethod("transformBuffer", DocTests::transformBuffer),
466     });
467   }
468 };
469 
470 } // Anonymous namespace
471 
472 // SECTION registration
JNI_OnLoad(JavaVM * vm,void *)473 jint JNI_OnLoad(JavaVM* vm, void*) {
474   return facebook::jni::initialize(vm, [] { DocTests::registerNatives(); });
475 }
476 // END
477