Skip to content

Commit 2a9d190

Browse files
committed
[[ AndroidListeners ]] Add function to check suitability of lcb handlers
- An LCB handler is suitable for a callback if it has the same number of input parameters as the callback handler and all its parameter typeinfos are compatible with the JObject typeinfo. - An LCB handler is suitable for creating a listener proxy if the listener has only one callback and the handler is suitable for that callback. - An array of LCB handlers is suitable for creating a listener proxy if each key in the array is the same as the name of a callback method of the listener and the handler value for that key is suitable for the callback with that name.
1 parent b5b5171 commit 2a9d190

File tree

4 files changed

+164
-19
lines changed

4 files changed

+164
-19
lines changed

docs/guides/LiveCode Builder Language Reference.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,10 +581,10 @@ interface. This effectively allows LCB handlers to be registered as the
581581
targets for java interface callbacks, such as event listeners.
582582

583583
The foreign handler binding to such a function takes a value that should
584-
either be a `Handler` or an `Array` - if it is a `Handler`, the handler
585-
will be called for all callbacks from the specified listener. An array
586-
can be used to assign different handlers to differently named callbacks
587-
to the specified listener.
584+
either be a `Handler` or an `Array` - if it is a `Handler`, the specified
585+
listener should only have one available callback. If the listener has
586+
multiple callbacks, an array can be used to assign handlers to each. Each
587+
key in the array must match the name of a callback in the listener.
588588

589589
For example:
590590

docs/lcb/notes/feature-android_listeners.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ The syntax is as follows:
1111

1212
foreign handler _JNI_CreateListener(in pMapping) returns JObject binds to "java:listener.class.path>interface()"
1313

14-
The foreign handler binding to such a function takes a value that should
15-
either be a `Handler` or an `Array` - if it is a `Handler`, the handler
16-
will be called for all callbacks from the specified listener. An array
17-
can be used to assign different handlers to differently named callbacks
18-
to the specified listener.
14+
The foreign handler binding to such a function takes a value that should
15+
either be a `Handler` or an `Array` - if it is a `Handler`, the specified
16+
listener should only have one available callback. If the listener has
17+
multiple callbacks, an array can be used to assign handlers to each. Each
18+
key in the array must match the name of a callback in the listener.
1919

2020
For example:
2121

libfoundation/src/foundation-java-private.cpp

Lines changed: 154 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ MCTypeInfoRef kMCJavaNativeMethodCallErrorTypeInfo;
206206
MCTypeInfoRef kMCJavaBindingStringSignatureErrorTypeInfo;
207207
MCTypeInfoRef kMCJavaCouldNotInitialiseJREErrorTypeInfo;
208208
MCTypeInfoRef kMCJavaJRENotSupportedErrorTypeInfo;
209+
MCTypeInfoRef kMCJavaInterfaceCallbackSignatureErrorTypeInfo;
209210

210211
bool MCJavaPrivateErrorsInitialize()
211212
{
@@ -223,6 +224,9 @@ bool MCJavaPrivateErrorsInitialize()
223224

224225
if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.java.JRENotSupported"), MCNAME("java"), MCSTR("Java Runtime Environment no supported with current configuration"), kMCJavaJRENotSupportedErrorTypeInfo))
225226
return false;
227+
228+
if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.java.InterfaceCallbackSignatureError"), MCNAME("java"), MCSTR("Handler for interface callback does not match callback signature"), kMCJavaInterfaceCallbackSignatureErrorTypeInfo))
229+
return false;
226230

227231
return true;
228232
}
@@ -234,6 +238,7 @@ void MCJavaPrivateErrorsFinalize()
234238
MCValueRelease(kMCJavaBindingStringSignatureErrorTypeInfo);
235239
MCValueRelease(kMCJavaCouldNotInitialiseJREErrorTypeInfo);
236240
MCValueRelease(kMCJavaJRENotSupportedErrorTypeInfo);
241+
MCValueRelease(kMCJavaInterfaceCallbackSignatureErrorTypeInfo);
237242
}
238243

239244
bool MCJavaPrivateErrorThrow(MCTypeInfoRef p_error_type)
@@ -1204,24 +1209,162 @@ static jclass MCJavaPrivateFindClass(MCNameRef p_class_name)
12041209
return s_env->FindClass(*t_class_cstring);
12051210
}
12061211

1207-
bool MCJavaCreateInterfaceProxy(MCNameRef p_class_name, MCTypeInfoRef p_signature, void *p_method_id, void *r_result, void **p_args, uindex_t p_arg_count)
1212+
static bool __MCJavaIsHandlerSuitableForListener(MCNameRef p_class_name, MCValueRef p_handlers)
12081213
{
1209-
if (MCHandlerTypeInfoGetParameterCount(p_signature) != 1)
1210-
return false;
1214+
jclass t_class_class = s_env->FindClass("java/lang/Class");
1215+
jmethodID t_get_methods = s_env->GetMethodID(t_class_class, "getMethods",
1216+
"()[Ljava/lang/reflect/Method;");
1217+
1218+
jclass t_class = MCJavaPrivateFindClass(p_class_name);
12111219

1212-
MCValueRef t_handlers = *(static_cast<MCValueRef *>(p_args[0]));
1213-
if (MCValueGetTypeCode(t_handlers) == kMCValueTypeCodeArray)
1220+
jobjectArray t_methods =
1221+
static_cast<jobjectArray>(s_env->CallObjectMethod(t_class,
1222+
t_get_methods));
1223+
1224+
jclass t_method_class = s_env->FindClass("java/lang/reflect/Method");
1225+
1226+
jmethodID t_get_parameters = s_env->GetMethodID(t_method_class,
1227+
"getParameterTypes",
1228+
"()[Ljava/lang/Class;");
1229+
1230+
// Lambda to check if a handler is suitable for the given method
1231+
auto t_check_handler = [&](MCHandlerRef p_handler, jobject p_method)
12141232
{
1215-
// Array of handlers for interface proxy
1233+
MCTypeInfoRef t_type_info = MCValueGetTypeInfo(p_handler);
1234+
1235+
// Ensure all callback handler parameters are of JavaObject type
1236+
uindex_t t_param_count = MCHandlerTypeInfoGetParameterCount(t_type_info);
1237+
1238+
for (uindex_t i = 0; i < t_param_count; ++i)
1239+
{
1240+
if (!__MCTypeInfoConformsToJavaType(MCHandlerTypeInfoGetParameterType(t_type_info, i),
1241+
kMCJavaTypeObject))
1242+
{
1243+
return MCErrorCreateAndThrowWithMessage(kMCJavaInterfaceCallbackSignatureErrorTypeInfo,
1244+
MCSTR("Callback handler %{handler} parameters must conform to JObject type"),
1245+
"handler", p_handler,
1246+
nullptr);
1247+
}
1248+
}
1249+
1250+
// Ensure the correct number of parameters
1251+
jobjectArray t_params =
1252+
static_cast<jobjectArray>(s_env->CallObjectMethod(p_method,
1253+
t_get_parameters));
1254+
uindex_t t_expected_param_count =
1255+
static_cast<uindex_t>(s_env->GetArrayLength(t_params));
1256+
if (t_param_count != t_expected_param_count)
1257+
{
1258+
MCAutoNumberRef t_exp;
1259+
if (!MCNumberCreateWithUnsignedInteger(t_expected_param_count,
1260+
&t_exp))
1261+
return false;
1262+
1263+
return MCErrorCreateAndThrowWithMessage(kMCJavaInterfaceCallbackSignatureErrorTypeInfo,
1264+
MCSTR("Wrong number of parameters for callback handler %{handler}: expected %{number}"),
1265+
"handler", p_handler,
1266+
"number", *t_exp,
1267+
nullptr);
1268+
}
1269+
1270+
return true;
1271+
};
1272+
1273+
uindex_t t_num_methods = s_env->GetArrayLength(t_methods);
1274+
if (t_num_methods == 0)
1275+
{
1276+
return MCErrorCreateAndThrowWithMessage(kMCJavaInterfaceCallbackSignatureErrorTypeInfo,
1277+
MCSTR("Target interface has no callback methods"),
1278+
nullptr);
12161279
}
1217-
else if (MCValueGetTypeCode(t_handlers) == kMCValueTypeCodeHandler)
1280+
1281+
if (MCValueGetTypeCode(p_handlers) == kMCValueTypeCodeArray)
12181282
{
1219-
// Single handler for listener interface
1283+
// Collect all the method names of this interface
1284+
jmethodID t_get_method_name = s_env->GetMethodID(t_method_class,
1285+
"getName",
1286+
"()Ljava/lang/String;");
1287+
MCAutoStringRefArray t_names;
1288+
for (uindex_t i = 0; i < t_num_methods; i++)
1289+
{
1290+
jobject t_object = s_env->GetObjectArrayElement(t_methods, i);
1291+
jstring t_name =
1292+
static_cast<jstring>(s_env->CallObjectMethod(t_object,
1293+
t_get_method_name));
1294+
MCAutoStringRef t_name_stringref;
1295+
if (!__MCJavaStringFromJString(t_name, &t_name_stringref))
1296+
return false;
1297+
1298+
if (!t_names.Push(*t_name_stringref))
1299+
return false;
1300+
}
1301+
1302+
// Array of handlers for interface proxy
1303+
uintptr_t t_iterator = 0;
1304+
MCNameRef t_key;
1305+
MCValueRef t_value;
1306+
while (MCArrayIterate(static_cast<MCArrayRef>(p_handlers),
1307+
t_iterator, t_key, t_value))
1308+
{
1309+
MCStringRef t_match = nullptr;
1310+
uindex_t j = 0;
1311+
for (; j < t_names.Size(); j++)
1312+
{
1313+
if (MCStringIsEqualTo(MCNameGetString(t_key),
1314+
t_names[j],
1315+
kMCStringOptionCompareCaseless))
1316+
{
1317+
t_match = t_names[j];
1318+
break;
1319+
}
1320+
}
1321+
if (t_match == nullptr)
1322+
{
1323+
// No method with matching name found
1324+
return MCErrorCreateAndThrowWithMessage(kMCJavaInterfaceCallbackSignatureErrorTypeInfo,
1325+
MCSTR("No callback method with name %{name}"),
1326+
"name", t_key,
1327+
nullptr);
1328+
}
1329+
1330+
// If we get here, we have a matching name, so check the handler
1331+
if (!t_check_handler(static_cast<MCHandlerRef>(t_value),
1332+
s_env->GetObjectArrayElement(t_methods, j)))
1333+
return false;
1334+
}
1335+
1336+
// If we get here, then all handlers were assigned to valid callbacks
1337+
// in the interface
1338+
return true;
1339+
12201340
}
1221-
else
1341+
else if (MCValueGetTypeCode(p_handlers) == kMCValueTypeCodeHandler)
12221342
{
1223-
return false;
1343+
// Only one handler provided - ensure there is only one callback
1344+
if (t_num_methods != 1)
1345+
{
1346+
return MCErrorCreateAndThrowWithMessage(kMCJavaInterfaceCallbackSignatureErrorTypeInfo,
1347+
MCSTR("Ambiguous callback assignment - target interface has multiple callback methods"),
1348+
nullptr);
1349+
}
1350+
1351+
return t_check_handler(static_cast<MCHandlerRef>(p_handlers),
1352+
s_env->GetObjectArrayElement(t_methods, 0));
12241353
}
1354+
1355+
// Value was not of correct type
1356+
return false;
1357+
}
1358+
1359+
bool MCJavaCreateInterfaceProxy(MCNameRef p_class_name, MCTypeInfoRef p_signature, void *p_method_id, void *r_result, void **p_args, uindex_t p_arg_count)
1360+
{
1361+
if (MCHandlerTypeInfoGetParameterCount(p_signature) != 1)
1362+
return false;
1363+
1364+
MCValueRef t_handlers = *(static_cast<MCValueRef *>(p_args[0]));
1365+
1366+
if (!__MCJavaIsHandlerSuitableForListener(p_class_name, t_handlers))
1367+
return false;
12251368

12261369
jclass t_inv_handler_class =
12271370
MCJavaPrivateFindClass(MCNAME("com.runrev.android.LCBInvocationHandler"));
@@ -1240,6 +1383,7 @@ bool MCJavaCreateInterfaceProxy(MCNameRef p_class_name, MCTypeInfoRef p_signatur
12401383
MCJavaObjectRef t_result_value;
12411384
if (!MCJavaObjectCreateNullableGlobalRef(t_proxy, t_result_value))
12421385
return false;
1386+
12431387
*(static_cast<MCJavaObjectRef *>(r_result)) = t_result_value;
12441388
return true;
12451389
}

libfoundation/src/foundation-java-private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,6 @@ extern MCTypeInfoRef kMCJavaNativeMethodCallErrorTypeInfo;
7474
extern MCTypeInfoRef kMCJavaBindingStringSignatureErrorTypeInfo;
7575
extern MCTypeInfoRef kMCJavaCouldNotInitialiseJREErrorTypeInfo;
7676
extern MCTypeInfoRef kMCJavaJRENotSupportedErrorTypeInfo;
77+
extern MCTypeInfoRef kMCJavaInterfaceCallbackSignatureErrorTypeInfo;
7778

7879
#endif

0 commit comments

Comments
 (0)