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 <fbjni/fbjni.h>
18 #include <condition_variable>
19 #include <mutex>
20
21 using namespace facebook::jni;
22
23 class TestException : public std::runtime_error {
24 public:
TestException()25 TestException() : std::runtime_error("fail") {}
26 };
27
28 // The C++ half of a hybrid class must extend HybridClass like this.
29 // HybridClass is default constructible.
30 class TestHybridClass : public facebook::jni::HybridClass<TestHybridClass> {
31 public:
32 static constexpr const char* const kJavaDescriptor =
33 "Lcom/facebook/jni/HybridTests$TestHybridClass;";
34
TestHybridClass()35 TestHybridClass() {}
36
TestHybridClass(int i,std::string s,bool b)37 TestHybridClass(int i, std::string s, bool b)
38 : i_(i), s_(std::move(s)), b_(b) {}
39
40 // The C++ implementation of initHybrid must be a static C++ method,
41 // since it has no C++ instance (it creates it). But it's a normal
42 // java method, because it gets a java this passed in (which is
43 // often unused). It must call makeCxxInstance and return its
44 // result.
45 static jhybriddata
initHybrid(alias_ref<jhybridobject>,jint i,jstring s,jboolean b)46 initHybrid(alias_ref<jhybridobject>, jint i, jstring s, jboolean b) {
47 // The arguments will be forwarded to the ctor, and the result
48 // will be saved in mHybridPointer in the java object.
49 return makeCxxInstance(i, wrap_alias(s)->toStdString(), b).release();
50 }
initHybrid2(alias_ref<jhybridobject>)51 static local_ref<jhybriddata> initHybrid2(alias_ref<jhybridobject>) {
52 return makeCxxInstance();
53 }
54 // It's also ok to make initHybrid work like a factory, which
55 // eliminates the need to define a ctor and reduces boilerplate
56 // code. This is a member, so fields can be initialized directly.
57 static local_ref<jhybriddata>
initHybrid3(alias_ref<jhybridobject>,std::string s,int i,bool b)58 initHybrid3(alias_ref<jhybridobject>, std::string s, int i, bool b) {
59 auto cxxPart = std::unique_ptr<TestHybridClass>(new TestHybridClass);
60 cxxPart->s_ = std::move(s);
61 cxxPart->i_ = i;
62 cxxPart->b_ = b;
63 return makeHybridData(std::move(cxxPart));
64 }
65
getInt()66 jint getInt() {
67 return i_;
68 }
69
getString()70 std::string getString() {
71 return s_;
72 }
73
getCharString()74 const char* getCharString() {
75 return s_.c_str();
76 }
77
setBoth(int i,std::string s)78 void setBoth(int i, std::string s) {
79 i_ = i;
80 s_ = s;
81 }
82
copy1(alias_ref<jhybridobject> other)83 bool copy1(alias_ref<jhybridobject> other) {
84 i_ = cthis(other)->i_;
85 s_ = cthis(other)->s_;
86 return true;
87 }
88
89 // This returns a boolean to test more code paths, not for any
90 // functional reason.
copy2(TestHybridClass * other)91 jboolean copy2(TestHybridClass* other) {
92 i_ = other->i_;
93 s_ = other->s_;
94 return true;
95 }
96
oops()97 void oops() {
98 throw TestException();
99 }
100
setGlobal(alias_ref<jstring> str)101 void setGlobal(alias_ref<jstring> str) {
102 global_ = make_global(str);
103 }
104
getGlobal1()105 global_ref<jstring> getGlobal1() {
106 return global_;
107 }
108
getGlobal2()109 const global_ref<jstring>& getGlobal2() {
110 return global_;
111 }
112
makeWithTwo(alias_ref<jclass> jthis)113 static jhybridobject makeWithTwo(alias_ref<jclass> jthis) {
114 // The args are passed to java, with autoconversion.
115 return newObjectJavaArgs(2, "two", true).release();
116 }
117
makeWithThree(alias_ref<jclass> jthis)118 static local_ref<jhybridobject> makeWithThree(alias_ref<jclass> jthis) {
119 // The args are passed to C++ initialization directly.
120 return newObjectCxxArgs(3, "three", true);
121 }
122
mapException(std::exception_ptr ex)123 static void mapException(std::exception_ptr ex) {
124 try {
125 std::rethrow_exception(ex);
126 } catch (const TestException& ex) {
127 throwNewJavaException("java/lang/ArrayStoreException", "");
128 }
129 }
130
autoconvertMany(alias_ref<jclass>)131 static void autoconvertMany(alias_ref<jclass>) {
132 // Autoconversion to jstring creates a local ref. Make sure is is
133 // released properly. The default local table size is 512.
134 for (int i = 0; i < 1000; ++i) {
135 newObjectJavaArgs(1000, "thousand", false);
136 newObjectJavaArgs(1001, make_jstring("thousand+1"), false);
137 }
138 }
139
140 // Declaring a register function for each class makes it easy to
141 // manage the process.
registerNatives()142 static void registerNatives() {
143 registerHybrid({
144 // Test registration of static method returning void.
145 makeNativeMethod("initHybrid", TestHybridClass::initHybrid),
146 // overloaded members are ugly to get pointers to. Use a
147 // different name for each C++ method to keep things readable.
148 makeNativeMethod("initHybrid", TestHybridClass::initHybrid2),
149 makeNativeMethod("initHybrid", TestHybridClass::initHybrid3),
150 // C++ members can be registered directly. The C++ instance
151 // created by makeCxxInstance will be retrieved from the
152 // HybridData, and the method will be invoked with the
153 // arguments passed to java. These test registration of
154 // method pointers returning non-void.
155 makeNativeMethod("getInt", TestHybridClass::getInt),
156 makeNativeMethod("getString", TestHybridClass::getString),
157 makeNativeMethod("getCharString", TestHybridClass::getCharString),
158 makeNativeMethod("setBoth", TestHybridClass::setBoth),
159 makeNativeMethod("copy1", TestHybridClass::copy1),
160 makeNativeMethod("copy2", TestHybridClass::copy2),
161 makeNativeMethod("oops", TestHybridClass::oops),
162 makeNativeMethod("setGlobal", TestHybridClass::setGlobal),
163 makeNativeMethod("getGlobal1", TestHybridClass::getGlobal1),
164 makeNativeMethod("getGlobal2", TestHybridClass::getGlobal2),
165 makeNativeMethod("makeWithTwo", TestHybridClass::makeWithTwo),
166 makeNativeMethod("makeWithThree", TestHybridClass::makeWithThree),
167 makeNativeMethod("autoconvertMany", TestHybridClass::autoconvertMany),
168 });
169 }
170
171 private:
172 friend HybridBase;
173
174 int i_ = 0;
175 std::string s_;
176 bool b_ = false;
177 global_ref<jstring> global_;
178 };
179
180 class AbstractTestHybrid
181 : public facebook::jni::HybridClass<AbstractTestHybrid> {
182 public:
183 static constexpr const char* const kJavaDescriptor =
184 "Lcom/facebook/jni/HybridTests$AbstractTestHybrid;";
185
AbstractTestHybrid(int nn)186 AbstractTestHybrid(int nn) : nativeNum_(nn) {}
187
nativeNum()188 int nativeNum() {
189 return nativeNum_;
190 }
191 // This is different than the java method!
192 virtual int sum() = 0;
193
registerNatives()194 static void registerNatives() {
195 registerHybrid({
196 makeNativeMethod("nativeNum", AbstractTestHybrid::nativeNum),
197 });
198 }
199
200 private:
201 int nativeNum_;
202 };
203
204 class ConcreteTestHybrid
205 : public facebook::jni::
206 HybridClass<ConcreteTestHybrid, AbstractTestHybrid> {
207 public:
208 static constexpr const char* const kJavaDescriptor =
209 "Lcom/facebook/jni/HybridTests$ConcreteTestHybrid;";
210
initHybrid(alias_ref<jclass>,int nn,int cn)211 static local_ref<jhybriddata> initHybrid(alias_ref<jclass>, int nn, int cn) {
212 return makeCxxInstance(nn, cn);
213 }
214
concreteNum()215 int concreteNum() {
216 return concreteNum_;
217 }
sum()218 int sum() override {
219 return nativeNum() + concreteNum();
220 }
221
registerNatives()222 static void registerNatives() {
223 registerHybrid({
224 makeNativeMethod("initHybrid", ConcreteTestHybrid::initHybrid),
225 makeNativeMethod("concreteNum", ConcreteTestHybrid::concreteNum),
226 });
227 }
228
229 private:
230 friend HybridBase;
ConcreteTestHybrid(int nn,int cn)231 ConcreteTestHybrid(int nn, int cn) : HybridBase(nn), concreteNum_(cn) {}
232
233 int concreteNum_;
234 };
235
cxxTestInheritance(alias_ref<jclass>,AbstractTestHybrid * ath)236 static jboolean cxxTestInheritance(alias_ref<jclass>, AbstractTestHybrid* ath) {
237 bool ret = true;
238
239 ret &= ath->nativeNum() == 5;
240 ret &= ath->sum() == 11;
241
242 auto cth = dynamic_cast<ConcreteTestHybrid*>(ath);
243
244 ret &= cth != nullptr;
245 ret &= cth->concreteNum() == 6;
246
247 return ret;
248 }
249
makeAbstractHybrid(alias_ref<jclass>)250 static local_ref<AbstractTestHybrid::jhybridobject> makeAbstractHybrid(
251 alias_ref<jclass>) {
252 auto cth = ConcreteTestHybrid::newObjectJavaArgs(0, 0, 0);
253 weak_ref<ConcreteTestHybrid::jhybridobject> wcth = make_weak(cth);
254 weak_ref<AbstractTestHybrid::jhybridobject> wath = wcth;
255
256 local_ref<AbstractTestHybrid::jhybridobject> ath = cth;
257 return ConcreteTestHybrid::newObjectJavaArgs(
258 cthis(ath)->nativeNum() + 7, 8, 9);
259 }
260
261 struct Base : public JavaClass<Base> {
262 static constexpr const char* const kJavaDescriptor =
263 "Lcom/facebook/jni/HybridTests$Base;";
264 };
265
266 class Derived : public HybridClass<Derived, Base> {
267 public:
268 static constexpr const char* const kJavaDescriptor =
269 "Lcom/facebook/jni/HybridTests$Derived;";
270
271 private:
272 friend HybridBase;
Derived()273 Derived() {}
274 };
275
276 struct Destroyable : public HybridClass<Destroyable> {
277 public:
278 static constexpr const char* const kJavaDescriptor =
279 "Lcom/facebook/jni/HybridTests$Destroyable;";
280
~DestroyableDestroyable281 ~Destroyable() override {
282 std::lock_guard<std::mutex> lk(*mtx_);
283 *dead_ = true;
284 cv_->notify_one();
285 }
286
287 using HybridBase::makeCxxInstance;
288
289 private:
290 friend HybridBase;
DestroyableDestroyable291 Destroyable(
292 std::shared_ptr<std::mutex> mtx,
293 std::shared_ptr<bool> dead,
294 std::shared_ptr<std::condition_variable> cv)
295 : mtx_(mtx), dead_(dead), cv_(cv) {}
296
297 std::shared_ptr<std::mutex> mtx_;
298 std::shared_ptr<bool> dead_;
299 std::shared_ptr<std::condition_variable> cv_;
300 };
301
cxxTestHybridDestruction(alias_ref<jclass>)302 static jboolean cxxTestHybridDestruction(alias_ref<jclass>) {
303 auto mtx = std::make_shared<std::mutex>();
304 auto dead = std::make_shared<bool>(false);
305 auto cv = std::make_shared<std::condition_variable>();
306 Destroyable::makeCxxInstance(mtx, dead, cv);
307 std::unique_lock<std::mutex> lk(*mtx);
308 cv->wait(lk, [&] { return dead; });
309 return JNI_TRUE;
310 }
311
cxxTestDerivedJavaClass(alias_ref<jclass>)312 static jboolean cxxTestDerivedJavaClass(alias_ref<jclass>) {
313 bool ret = true;
314
315 auto derivedJava = Derived::newObjectCxxArgs();
316
317 ret &= derivedJava ? true : false;
318 ret &= derivedJava->isInstanceOf(Derived::javaClassLocal());
319 ret &= derivedJava->isInstanceOf(Base::javaClassLocal());
320
321 auto javaPtr = derivedJava.get();
322
323 ret &= dynamic_cast<Derived::javaobject>(javaPtr) != nullptr;
324 ret &= dynamic_cast<Base::javaobject>(javaPtr) != nullptr;
325 ret &= dynamic_cast<jobject>(javaPtr) != nullptr;
326
327 return ret;
328 }
329
330 class TestHybridClassBase
331 : public facebook::jni::HybridClass<TestHybridClassBase> {
332 public:
333 static constexpr const char* const kJavaDescriptor =
334 "Lcom/facebook/jni/HybridTests$TestHybridClassBase;";
335
TestHybridClassBase()336 TestHybridClassBase() {}
337
TestHybridClassBase(int i)338 explicit TestHybridClassBase(int i) : i_(i) {}
339
initHybrid(alias_ref<jhybridobject> o,jint i)340 static void initHybrid(alias_ref<jhybridobject> o, jint i) {
341 // The arguments will be forwarded to the ctor, and the result
342 // will be saved in mHybridPointer in the java object.
343 setCxxInstance(o, i);
344 }
345
initHybrid2(alias_ref<jhybridobject> o)346 static void initHybrid2(alias_ref<jhybridobject> o) {
347 setCxxInstance(o);
348 }
349
getInt()350 virtual jint getInt() {
351 return i_;
352 }
353
setInt(int i)354 void setInt(int i) {
355 i_ = i;
356 }
357
makeWithThree(alias_ref<jclass>)358 static local_ref<jhybridobject> makeWithThree(
359 alias_ref<jclass> /* unused */) {
360 // The args are passed to C++ initialization directly.
361 return newObjectCxxArgs(3);
362 }
363
registerNatives()364 static void registerNatives() {
365 registerHybrid({
366 makeNativeMethod("initHybrid", TestHybridClassBase::initHybrid),
367 makeNativeMethod("initHybrid", TestHybridClassBase::initHybrid2),
368 makeNativeMethod("getInt", TestHybridClassBase::getInt),
369 makeNativeMethod("setInt", TestHybridClassBase::setInt),
370 makeNativeMethod("makeWithThree", TestHybridClassBase::makeWithThree),
371 });
372 }
373
374 private:
375 int i_ = 0;
376 };
377
RegisterTestHybridClass()378 void RegisterTestHybridClass() {
379 TestHybridClass::registerNatives();
380 AbstractTestHybrid::registerNatives();
381 ConcreteTestHybrid::registerNatives();
382 TestHybridClassBase::registerNatives();
383
384 registerNatives(
385 "com/facebook/jni/HybridTests",
386 {
387 makeNativeMethod("cxxTestInheritance", cxxTestInheritance),
388 makeNativeMethod("makeAbstractHybrid", makeAbstractHybrid),
389 makeNativeMethod("cxxTestDerivedJavaClass", cxxTestDerivedJavaClass),
390 makeNativeMethod(
391 "cxxTestHybridDestruction", cxxTestHybridDestruction),
392 });
393 }
394