1+ /*
2+ * FXGL - JavaFX Game Library. The MIT License (MIT).
3+ * Copyright (c) AlmasB (almaslvl@gmail.com).
4+ * See LICENSE for details.
5+ */
6+
7+ package com.almasb.fxgl.core.reflect
8+
9+ import java.lang.RuntimeException
10+ import java.lang.reflect.Method
11+ import java.util.function.BiFunction
12+
13+ /* *
14+ * Allows calling methods of any object using its String name
15+ * and String arguments.
16+ *
17+ * @author Almas Baim (https://github.com/AlmasB)
18+ */
19+ class ReflectionFunctionCaller {
20+
21+ private val functions = hashMapOf<FunctionSignature , ReflectionFunction >()
22+
23+ /* *
24+ * Provides conversions from [String] to other types.
25+ */
26+ private val stringToObject = hashMapOf<Class <* >, (String ) -> Any > ()
27+
28+ var defaultFunctionHandler: BiFunction <String , List <String >, Any > = BiFunction { name, args ->
29+ throw RuntimeException (" No function handler for $name with $args " )
30+ }
31+
32+ val methods: List <Method >
33+ get() = functions.values.map { it.method }
34+
35+ init {
36+ // register common types
37+ stringToObject[String ::class .java] = { it }
38+ stringToObject[Int ::class .java] = { it.toInt() }
39+ stringToObject[Double ::class .java] = { it.toDouble() }
40+ stringToObject[Boolean ::class .java] = {
41+ when (it) {
42+ " true" -> true
43+ " false" -> false
44+ else -> throw RuntimeException (" Cannot convert $it to Boolean" )
45+ }
46+ }
47+ stringToObject[List ::class .java] = { it.split(" ," ) }
48+ }
49+
50+ fun <T : Any > addStringToObjectConverter (type : Class <T >, converter : (String ) -> T ) {
51+ stringToObject[type] = converter
52+ }
53+
54+ fun removeStringToObjectConverter (type : Class <* >) {
55+ stringToObject.remove(type)
56+ }
57+
58+ /* *
59+ * Add all methods (incl. private) of [targetObject] to be invokable
60+ * by this reflection function caller.
61+ */
62+ fun addFunctionCallTarget (targetObject : Any ) {
63+ targetObject.javaClass.declaredMethods.forEach {
64+ val signature = FunctionSignature (it.name, it.parameterCount)
65+ val method = ReflectionFunction (targetObject, it)
66+ it.isAccessible = true
67+
68+ functions[signature] = method
69+ }
70+ }
71+
72+ /* *
73+ * Remove all methods of a previously added [targetObject].
74+ */
75+ fun removeFunctionCallTarget (targetObject : Any ) {
76+ functions.filterValues { it.functionCallTarget == = targetObject }
77+ .forEach { functions.remove(it.key) }
78+ }
79+
80+ /* *
81+ * @return true if [functionName] with [paramCount] number of parameters exists
82+ */
83+ fun exists (functionName : String , paramCount : Int ): Boolean {
84+ return functions.containsKey(FunctionSignature (functionName, paramCount))
85+ }
86+
87+ fun call (functionName : String , args : List <String >): Any {
88+ return call(functionName, args.toTypedArray())
89+ }
90+
91+ fun call (functionName : String , args : Array <String >): Any {
92+ val function = functions[FunctionSignature (functionName, args.size)]
93+
94+ if (function != null ) {
95+ val argsAsObjects = function.method.parameterTypes.mapIndexed { index, type ->
96+ val converter = stringToObject[type] ? : throw java.lang.RuntimeException (" No converter found from String to $type " )
97+ converter.invoke(args[index])
98+ }
99+
100+ // void returns null, but Any is expected, so we return 0 in such cases
101+ return function.method.invoke(function.functionCallTarget, * argsAsObjects.toTypedArray()) ? : 0
102+ }
103+
104+ return defaultFunctionHandler.apply (functionName, args.toList())
105+ }
106+
107+ private data class FunctionSignature (val name : String , val paramCount : Int )
108+
109+ /* *
110+ * Stores the object [functionCallTarget] and the function [method] that can be invoked on the object.
111+ */
112+ private data class ReflectionFunction (val functionCallTarget : Any , val method : Method )
113+ }
0 commit comments