Last active
June 25, 2019 20:47
-
-
Save jaredrummler/c5e7c212ec54a149b7e69da3967e4d4b to your computer and use it in GitHub Desktop.
Java reflection made easy using Kotlin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.reflect.* | |
object Reflect { | |
private val cache = mutableMapOf<String, AccessibleObject>() | |
/** | |
* Get a method from a class | |
* | |
* @param obj | |
* the object or class. | |
* @param name | |
* the requested method's name | |
* @param types | |
* the parameter types of the requested method. | |
* @return a [Method] object which represents the method matching the specified name and parameter types | |
*/ | |
fun getMethod(obj: Any?, name: String, vararg types: Class<*>): Method? { | |
if (obj == null) return null | |
val key = cacheKey(obj, name, *types) | |
return fromCache(key) ?: run { | |
var klass: Class<*>? = obj as? Class<*> ?: obj.javaClass | |
while (klass != null) { | |
try { | |
klass.getDeclaredMethod(name, *types).let { method -> | |
if (!method.isAccessible) method.isAccessible = true | |
cache[key] = method | |
return method | |
} | |
} catch (ignored: NoSuchMethodException) { | |
} | |
klass = klass.superclass | |
} | |
null | |
} | |
} | |
/** | |
* Get a field from a class. | |
* | |
* @param obj | |
* the object or class | |
* @param name | |
* the name of the field | |
* @return a [Field] object for the field with the given name which is declared in the class. | |
*/ | |
fun getField(obj: Any?, name: String): Field? { | |
if (obj == null) return null | |
val key = cacheKey(obj, name) | |
return fromCache(key) ?: run { | |
var klass: Class<*>? = obj as? Class<*> ?: obj.javaClass | |
while (klass != null) { | |
try { | |
klass.getDeclaredField(name).let { field -> | |
if (!field.isAccessible) field.isAccessible = true | |
cache[key] = field | |
return field | |
} | |
} catch (ignored: NoSuchFieldException) { | |
} | |
klass = klass.superclass | |
} | |
null | |
} | |
} | |
/** | |
* Returns a [Constructor] object | |
* | |
* @param parameterTypes the parameter array | |
* @return The [Constructor] object for the constructor with the specified parameter list | |
*/ | |
inline fun <reified T> getConstructor(vararg parameterTypes: Class<*>): Constructor<T>? = try { | |
T::class.java.getDeclaredConstructor(*parameterTypes).also { | |
if (!it.isAccessible) it.isAccessible = true | |
} | |
} catch (e: Exception) { | |
null | |
} | |
/** | |
* Dynamically invoke a method. | |
* | |
* @param obj | |
* The object or class to get the method from. | |
* @param name | |
* The name of the method to invoke. | |
* @param types | |
* the parameter types of the requested method. | |
* @param args | |
* the arguments to the method | |
* @param <T> | |
* the method's return type | |
* @return the result of dynamically invoking this method. | |
*/ | |
inline fun <reified T> invoke( | |
obj: Any?, | |
name: String, | |
types: Array<Class<*>> = emptyArray(), | |
vararg args: Any | |
): T? = | |
try { | |
getMethod(obj, name, *types)?.run { | |
invoke(obj, *args) as? T | |
} | |
} catch (e: Exception) { | |
null | |
} | |
/** | |
* Get the value from a field in an object/class. | |
* | |
* @param obj | |
* the object or class | |
* @param name | |
* the name of the field | |
* @param <T> | |
* The field's type | |
* @return the value of the field in the specified object. | |
*/ | |
inline fun <reified T> getFieldValue(obj: Any?, name: String): T? = getField(obj, name)?.let { field -> | |
try { | |
if (!field.isAccessible) field.isAccessible = true | |
field.get(obj) as? T | |
} catch (e: IllegalAccessException) { | |
null | |
} | |
} | |
/** | |
* Set the field value to the specified value. | |
* | |
* @param field | |
* the field | |
* @param obj | |
* the object whose field should be modified | |
* @param value | |
* the new value for the field of `obj` being modified | |
* @return `true` if the field was set successfully. | |
*/ | |
fun setFieldValue(field: Field, obj: Any?, value: Any?): Boolean = try { | |
if (!field.isAccessible) field.isAccessible = true | |
if (Modifier.isFinal(field.modifiers)) { | |
val modifiersField = Field::class.java.getDeclaredField("modifiers") | |
modifiersField.isAccessible = true | |
modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv()) | |
} | |
field.set(obj, value) | |
true | |
} catch (e: IllegalAccessException) { | |
false | |
} | |
private inline fun <reified T> fromCache(key: String): T? = cache[key] as? T | |
private fun cacheKey(obj: Any, name: String, vararg types: Class<*>) = StringBuilder().apply { | |
val klass = obj as? Class<*> ?: obj::class.java | |
append(klass.name).append('#').append(name) | |
if (types.isNotEmpty()) { | |
append('(') | |
var separator = "" | |
types.forEach { | |
append(separator).append(it.name) | |
separator = ", " | |
} | |
append(')') | |
} | |
}.toString() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment