浏览代码

Implement `has_java_method(...)` for `JavaClassWrapper` and `JNISingleton`

Fredia Huya-Kouadio 4 月之前
父节点
当前提交
0622cee189

+ 9 - 0
doc/classes/JNISingleton.xml

@@ -9,4 +9,13 @@
 	<tutorials>
 		<link title="Creating Android plugins">$DOCS_URL/tutorials/platform/android/android_plugin.html#doc-android-plugin</link>
 	</tutorials>
+	<methods>
+		<method name="has_java_method" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="method" type="StringName" />
+			<description>
+				Returns [code]true[/code] if the given [param method] name exists in the JNISingleton's Java methods.
+			</description>
+		</method>
+	</methods>
 </class>

+ 7 - 0
doc/classes/JavaClass.xml

@@ -29,5 +29,12 @@
 				Returns a [JavaClass] representing the Java parent class of this class.
 			</description>
 		</method>
+		<method name="has_java_method" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="method" type="StringName" />
+			<description>
+				Returns [code]true[/code] if the given [param method] name exists in the object's Java methods.
+			</description>
+		</method>
 	</methods>
 </class>

+ 7 - 0
doc/classes/JavaObject.xml

@@ -17,5 +17,12 @@
 				Returns the [JavaClass] that this object is an instance of.
 			</description>
 		</method>
+		<method name="has_java_method" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="method" type="StringName" />
+			<description>
+				Returns [code]true[/code] if the given [param method] name exists in the object's Java methods.
+			</description>
+		</method>
 	</methods>
 </class>

+ 10 - 0
platform/android/api/api.cpp

@@ -62,10 +62,12 @@ void JavaClass::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_java_class_name"), &JavaClass::get_java_class_name);
 	ClassDB::bind_method(D_METHOD("get_java_method_list"), &JavaClass::get_java_method_list);
 	ClassDB::bind_method(D_METHOD("get_java_parent_class"), &JavaClass::get_java_parent_class);
+	ClassDB::bind_method(D_METHOD("has_java_method", "method"), &JavaClass::has_java_method);
 }
 
 void JavaObject::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_java_class"), &JavaObject::get_java_class);
+	ClassDB::bind_method(D_METHOD("has_java_method", "method"), &JavaObject::has_java_method);
 }
 
 void JavaClassWrapper::_bind_methods() {
@@ -94,6 +96,10 @@ Ref<JavaClass> JavaClass::get_java_parent_class() const {
 	return Ref<JavaClass>();
 }
 
+bool JavaClass::has_java_method(const StringName &) const {
+	return false;
+}
+
 JavaClass::JavaClass() {
 }
 
@@ -108,6 +114,10 @@ Ref<JavaClass> JavaObject::get_java_class() const {
 	return Ref<JavaClass>();
 }
 
+bool JavaObject::has_java_method(const StringName &) const {
+	return false;
+}
+
 JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
 
 Ref<JavaClass> JavaClassWrapper::_wrap(const String &, bool) {

+ 4 - 2
platform/android/api/java_class_wrapper.h

@@ -200,6 +200,7 @@ public:
 	String get_java_class_name() const;
 	TypedArray<Dictionary> get_java_method_list() const;
 	Ref<JavaClass> get_java_parent_class() const;
+	bool has_java_method(const StringName &p_method) const;
 
 #ifdef ANDROID_ENABLED
 	virtual String to_string() override;
@@ -226,6 +227,7 @@ public:
 	virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
 
 	Ref<JavaClass> get_java_class() const;
+	bool has_java_method(const StringName &p_method) const;
 
 #ifdef ANDROID_ENABLED
 	virtual String to_string() override;
@@ -244,7 +246,7 @@ class JavaClassWrapper : public Object {
 #ifdef ANDROID_ENABLED
 	RBMap<String, Ref<JavaClass>> class_cache;
 	friend class JavaClass;
-	jmethodID Class_getDeclaredConstructors;
+	jmethodID Class_getConstructors;
 	jmethodID Class_getDeclaredMethods;
 	jmethodID Class_getFields;
 	jmethodID Class_getName;
@@ -272,7 +274,7 @@ class JavaClassWrapper : public Object {
 
 	Ref<JavaObject> exception;
 
-	Ref<JavaClass> _wrap(const String &p_class, bool p_allow_private_methods_access);
+	Ref<JavaClass> _wrap(const String &p_class, bool p_allow_non_public_methods_access);
 
 	static JavaClassWrapper *singleton;
 

+ 42 - 17
platform/android/api/jni_singleton.h

@@ -46,35 +46,60 @@ class JNISingleton : public Object {
 	RBMap<StringName, MethodData> method_map;
 	Ref<JavaObject> wrapped_object;
 
+protected:
+	static void _bind_methods() {
+		ClassDB::bind_method(D_METHOD("has_java_method", "method"), &JNISingleton::has_java_method);
+	}
+
 public:
 	virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
-		if (wrapped_object.is_valid()) {
-			RBMap<StringName, MethodData>::Element *E = method_map.find(p_method);
-
-			// Check the method we're looking for is in the JNISingleton map and that
-			// the arguments match.
-			bool call_error = !E || E->get().argtypes.size() != p_argcount;
-			if (!call_error) {
-				for (int i = 0; i < p_argcount; i++) {
-					if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
-						call_error = true;
-						break;
+		// Godot methods take precedence.
+		Variant ret = Object::callp(p_method, p_args, p_argcount, r_error);
+		if (r_error.error == Callable::CallError::CALL_OK) {
+			return ret;
+		}
+
+		// Check the method we're looking for is in the JNISingleton map.
+		RBMap<StringName, MethodData>::Element *E = method_map.find(p_method);
+		if (E) {
+			if (wrapped_object.is_valid()) {
+				// Check that the arguments match.
+				int method_arg_count = E->get().argtypes.size();
+				if (p_argcount < method_arg_count) {
+					r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
+				} else if (p_argcount > method_arg_count) {
+					r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
+				} else {
+					// Check the arguments are valid.
+					bool arguments_valid = true;
+					for (int i = 0; i < p_argcount; i++) {
+						if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
+							arguments_valid = false;
+							break;
+						}
 					}
-				}
-			}
 
-			if (!call_error) {
-				return wrapped_object->callp(p_method, p_args, p_argcount, r_error);
+					if (arguments_valid) {
+						return wrapped_object->callp(p_method, p_args, p_argcount, r_error);
+					} else {
+						r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+					}
+				}
+			} else {
+				r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
 			}
 		}
-
-		return Object::callp(p_method, p_args, p_argcount, r_error);
+		return Variant();
 	}
 
 	Ref<JavaObject> get_wrapped_object() const {
 		return wrapped_object;
 	}
 
+	bool has_java_method(const StringName &p_method) const {
+		return method_map.has(p_method);
+	}
+
 	void add_method(const StringName &p_name, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) {
 		MethodData md;
 		md.argtypes = p_args;

+ 1 - 1
platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt

@@ -51,7 +51,7 @@ class AndroidRuntimePlugin(godot: Godot) : GodotPlugin(godot) {
 	 * Provides access to the host [android.app.Activity] to GDScript
 	 */
 	@UsedByGodot
-	override fun getActivity() = super.getActivity()
+	public override fun getActivity() = super.getActivity()
 
 	/**
 	 * Utility method used to create [Runnable] from Godot [Callable].

+ 52 - 8
platform/android/java_class_wrapper.cpp

@@ -36,6 +36,7 @@
 bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret) {
 	HashMap<StringName, List<MethodInfo>>::Iterator M = methods.find(p_method);
 	if (!M) {
+		r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
 		return false;
 	}
 
@@ -756,7 +757,11 @@ bool JavaClass::_get(const StringName &p_name, Variant &r_ret) const {
 }
 
 Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
-	Variant ret;
+	// Godot methods take precedence.
+	Variant ret = RefCounted::callp(p_method, p_args, p_argcount, r_error);
+	if (r_error.error == Callable::CallError::CALL_OK) {
+		return ret;
+	}
 
 	String method = (p_method == java_constructor_name) ? "<init>" : p_method;
 	bool found = _call_method(nullptr, method, p_args, p_argcount, r_error, ret);
@@ -764,7 +769,7 @@ Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int
 		return ret;
 	}
 
-	return RefCounted::callp(p_method, p_args, p_argcount, r_error);
+	return Variant();
 }
 
 String JavaClass::get_java_class_name() const {
@@ -858,6 +863,11 @@ String JavaClass::to_string() {
 	return "<JavaClass:" + java_class_name + ">";
 }
 
+bool JavaClass::has_java_method(const StringName &p_method) const {
+	String method = (p_method == java_constructor_name) ? "<init>" : p_method;
+	return methods.has(method);
+}
+
 JavaClass::JavaClass() {
 }
 
@@ -873,10 +883,15 @@ JavaClass::~JavaClass() {
 /////////////////////
 
 Variant JavaObject::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+	// Godot methods take precedence.
+	Variant ret = RefCounted::callp(p_method, p_args, p_argcount, r_error);
+	if (r_error.error == Callable::CallError::CALL_OK) {
+		return ret;
+	}
+
 	if (instance) {
 		Ref<JavaClass> c = base_class;
 		while (c.is_valid()) {
-			Variant ret;
 			bool found = c->_call_method(this, p_method, p_args, p_argcount, r_error, ret);
 			if (found) {
 				return ret;
@@ -885,7 +900,7 @@ Variant JavaObject::callp(const StringName &p_method, const Variant **p_args, in
 		}
 	}
 
-	return RefCounted::callp(p_method, p_args, p_argcount, r_error);
+	return Variant();
 }
 
 Ref<JavaClass> JavaObject::get_java_class() const {
@@ -899,6 +914,19 @@ String JavaObject::to_string() {
 	return RefCounted::to_string();
 }
 
+bool JavaObject::has_java_method(const StringName &p_method) const {
+	if (instance) {
+		Ref<JavaClass> c = base_class;
+		while (c.is_valid()) {
+			if (c->has_java_method(p_method)) {
+				return true;
+			}
+			c = c->get_java_parent_class();
+		}
+	}
+	return false;
+}
+
 JavaObject::JavaObject() {
 }
 
@@ -1461,7 +1489,7 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
 	return false;
 }
 
-Ref<JavaClass> JavaClassWrapper::_wrap(const String &p_class, bool p_allow_private_methods_access) {
+Ref<JavaClass> JavaClassWrapper::_wrap(const String &p_class, bool p_allow_non_public_methods_access) {
 	String class_name_dots = p_class.replace_char('/', '.');
 	if (class_cache.has(class_name_dots)) {
 		return class_cache[class_name_dots];
@@ -1473,10 +1501,18 @@ Ref<JavaClass> JavaClassWrapper::_wrap(const String &p_class, bool p_allow_priva
 	jclass bclass = jni_find_class(env, class_name_dots.replace_char('.', '/').utf8().get_data());
 	ERR_FAIL_NULL_V_MSG(bclass, Ref<JavaClass>(), vformat("Java class '%s' not found.", p_class));
 
-	jobjectArray constructors = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredConstructors);
+	jobjectArray constructors = (jobjectArray)env->CallObjectMethod(bclass, Class_getConstructors);
+	if (env->ExceptionCheck()) {
+		env->ExceptionDescribe();
+		env->ExceptionClear();
+	}
 	ERR_FAIL_NULL_V(constructors, Ref<JavaClass>());
 
 	jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredMethods);
+	if (env->ExceptionCheck()) {
+		env->ExceptionDescribe();
+		env->ExceptionClear();
+	}
 	ERR_FAIL_NULL_V(methods, Ref<JavaClass>());
 
 	Ref<JavaClass> java_class = memnew(JavaClass);
@@ -1515,8 +1551,9 @@ Ref<JavaClass> JavaClassWrapper::_wrap(const String &p_class, bool p_allow_priva
 		Vector<String> params;
 
 		jint mods = env->CallIntMethod(obj, is_constructor ? Constructor_getModifiers : Method_getModifiers);
+		bool is_public = (mods & 0x0001) != 0; // java.lang.reflect.Modifier.PUBLIC
 
-		if (!(mods & 0x0001) && (is_constructor || !p_allow_private_methods_access)) {
+		if (!is_public && (is_constructor || !p_allow_non_public_methods_access)) {
 			env->DeleteLocalRef(obj);
 			continue; //not public bye
 		}
@@ -1627,6 +1664,13 @@ Ref<JavaClass> JavaClassWrapper::_wrap(const String &p_class, bool p_allow_priva
 				mi.method = env->GetMethodID(bclass, str_method.utf8().get_data(), signature.utf8().get_data());
 			}
 
+			if (env->ExceptionCheck()) {
+				// Exceptions may be thrown when trying to access hidden methods; write the exception to the logs and continue.
+				env->ExceptionDescribe();
+				env->ExceptionClear();
+				continue;
+			}
+
 			ERR_CONTINUE(!mi.method);
 
 			java_class->methods[str_method].push_back(mi);
@@ -1700,7 +1744,7 @@ JavaClassWrapper::JavaClassWrapper() {
 	ERR_FAIL_NULL(env);
 
 	jclass bclass = jni_find_class(env, "java/lang/Class");
-	Class_getDeclaredConstructors = env->GetMethodID(bclass, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;");
+	Class_getConstructors = env->GetMethodID(bclass, "getConstructors", "()[Ljava/lang/reflect/Constructor;");
 	Class_getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
 	Class_getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
 	Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");