- Notifications
You must be signed in to change notification settings - Fork0
goatbytes/KFlect
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
KFlect is a Kotlin library that provides dynamic access to class members. It enables you to inspect and manipulate classes at runtime, including private members, companion objects, and extension functions. This can be useful for testing, debugging, and advanced programming techniques.
Add the following to yourbuild.gradle.kts
in your project:
dependencies { implementation("io.goatbytes:kflect:1.0.2")}
This guide demonstrates howkflect
provides an efficient approach to interacting with classes dynamically. Using aPerson
class as our example, we’ll walk through instance creation, modifying private members, invoking companion object functions, and even accessing top-level extension functions.
Here’s a simplePerson
class with a private constructor, private fields, and a private function. Normally, these members would be inaccessible from the outside, but we can work with them flexibly throughkflect
.
packageio.goatbytes.kflect.exampleclassPerson private constructor(valname:String,valage:Int,privatevalssn:String// Private property) {constructor(name:String, age:Int):this(name, age, generateSSN())fungetIntroduction():String="Hi, I'm$name and I'm$age years old."privatefungetPrivateInfo():String="SSN:$ssn"companionobject {privatefungenerateSSN():String {returnlistOf(3,2,4).joinToString("-") { (1..it).joinToString("") { (1..9).random().toString() } } } }}// Top-level extension functionsfun Person.greet()=println("Hello,$name!")privatefun Person.is21()= age>=21
Withkflect
, you can create an instance ofPerson
even though the constructor is private:
kflect {val person="io.goatbytes.kflect.example.Person"("Jared",39)asPersonprintln(person.getIntroduction())// Output: "Hi, I'm Jared and I'm 39 years old."}
Sometimes, it’s necessary to read or modify private fields or call private methods.kflect
allows this by letting us reach into the class and interact with its internal details:
kflect {val getPrivateInfo= person.function("getPrivateInfo")println(getPrivateInfo(person))// Output: "SSN: XXX-XX-XXXX"val generateSSN= person.function("generateSSN") person["ssn"]= generateSSN()println(getPrivateInfo(person))// Updated SSN}
This method is particularly useful for testing, debugging, and working with data that would otherwise be restricted.
We can also access companion object functions, including private ones. Here’s an example of calling the privategenerateSSN
method:
kflect {val generateSSN= person.function("generateSSN")println("Generated SSN:${generateSSN()}")}
You can even invoke top-level extension functions dynamically. Here’s how we usegreet
and check theis21
function:
kflect {val greet= person.topLevelExtensionFunction(Person::class,"greet") greet(person)// Output: "Hello, Jared!"val is21= person.topLevelExtensionFunction(Person::class,"is21")if (is21(person)==true) {println("${person.name} is old enough to attend the conference.") }else {println("${person.name} isn't old enough for the conference yet.") }}
Withkflect
, you can simplify standard reflection tasks, such as invoking a method by name or accessing private fields in Java. This flexibility can save time during debugging or when working with restricted APIs.
val substringMethod="java.lang.String".method("substring",Int::class,Int::class)val result= substringMethod("Hello, World!",7,12)println(result)// Output: "World"
In this example, we’re callingsubstring
on aString
instance directly by the method name, which reduces the usual reflection boilerplate.
val person="com.example.Person"("Jane Doe",25)asPersonperson["privateField"]="newValue"// Modify a private field directly
Here,kflect
enables modification of private fields without the usualsetAccessible
calls, making code more readable and concise.
On Android,kflect
becomes particularly valuable when working with the platform's reflection-heavy APIs, such as accessing hidden fields or methods in the Android SDK. Here’s an example of how to usekflect
for tasks like obtaining the current activity:
Android’s framework classes often require reflection for accessing certain hidden APIs or working with internal details. Usingkflect
, you can dynamically obtain the currentActivity
without direct references toActivityThread
:
val currentActivity:Activity= kflect { ("android.app.ActivityThread"("currentActivityThread")["mActivities"]asMap<*,*>) .values.firstNotNullOf { record->when (record["paused"]==false) {true-> record["activity"]asActivity// return the active android.app.Activityelse->null } }}
This example is practical for use in debugging scenarios, dynamic feature delivery, and analytics libraries where obtaining the current activity context is essential.
Thepredicates
package in KFlect enables you to filter and search class members—like methods, properties, fields, and constructors—using declarative conditions. This approach simplifies reflection by enablingselective access to class members based on your specific criteria.
KFlect’s predicates provide a highly readable,fluent interface for querying classes. Rather than manually iterating through members, predicates offer a way to dynamically filter the members you need, reducing boilerplate and increasing readability.
KFlect’spredicates
package offers a range of filters that target different kinds of class members. Each predicate can be chained to form complex criteria, providing you with fine-grained control over member selection.
Here’s a quick overview of the available predicate classes:
- ConstructorPredicates: Filters constructors based on parameters, annotations, and accessibility.
- ExecutablePredicates: General filters for executable members, like functions and methods.
- FieldPredicates: Allows filtering fields based on type, visibility, and annotations.
- KCallablePredicates: A generic set of filters for Kotlin callables, covering properties and functions.
- KFunctionPredicates: Targets Kotlin-specific functions with conditions like return type and parameters.
- KPropertyPredicates: Specific to properties, with filters for mutability, visibility, and initialization.
- MemberPredicates: Broad member-level predicates to filter by name, annotations, and accessibility.
- MethodPredicates: Focused on Java methods, filtering by return type, parameters, and annotations.
Using KFlect’s predicates, you can filter and select specific members of a class without looping manually. Below are some examples illustrating various use cases.
// Find functions in `Random` with a `Unit` return type and an `Int` parameterval functions=Random::class.filterFunctions { returnType(Unit::class)and hasParameterType(Int::class)}// Filter public methods in `String` with one parameter and a return type of `String`val methods=String::class.java.filterMethods { hasParameterCount(1)and returnType(String::class.java)and isPublic()}// Locate a property in `StringBuilder` with an `Int` return type and the name "length"val property=StringBuilder::class.findProperty { returnType(Int::class)and name("length")}// Find a method in `StringBuilder` named "length" with an `Int` return typeval method=StringBuilder::class.java.findMethod { name("length")and returnType(Int::class.java)}
In these examples,filterFunctions
,filterMethods
,findProperty
, andfindMethod
leverage predicates to locate members matching specific conditions.
KFlect’s predicates can be chained usingand
,or
, andnot
, making it easy to build complex conditions. For instance:
val functionsWithConditions=MyClass::class.filterFunctions { returnType(Unit::class)and (isPublic()or hasAnnotation<Deprecated>())}
This query retrieves allUnit
-returning functions inString
that are eitherpublic
or annotated with@Deprecated
.
KFlect includesLazyKFlect
andSynchronizedLazyKFlect
for situations whereon-demand initialization of reflection data is preferred. These classes are particularly useful for performance-sensitive applications or cases where reflection data might not be required immediately.
LazyKFlect
enables lazy initialization of reflection-related objects, ensuring that they’re only created when accessed for the first time. This approach minimizes the initial memory footprint and computation cost, as resources are allocated only when necessary.
val lazyReflection byLazyKFlect {"io.goatbytes.kflect.example.Person"("Alice",28)asPerson }
Formultithreaded environments,SynchronizedLazyKFlect
provides thread-safe lazy initialization by ensuring that only one thread can initialize the reflection data at a time.
val synchronizedLazyReflection bySynchronizedLazyKFlect {"io.goatbytes.kflect.example.Person"("Bob",35)asPerson }
src└── main └── kotlin └── io └── goatbytes └── kflect ├── cache // Caching mechanism for reflection data ├── dsl // DSL for simplified reflection queries ├── exceptions // Custom exceptions for reflection handling ├── ext // Extension functions for reflection utilities ├── lazy // Lazy initialization utilities for reflection ├── misc // Miscellaneous helpers, like unsafe operations ├── os // OS-specific utilities ├── predicates // Predicates for filtering reflection data └── traverser // Traversal utilities for classes and functions └── ext // Extensions supporting traversal operations
Contributions are welcome! Please read ourcontributing guide and submit pullrequests to our repository.
This project is licensed under the Apache 2.0 License — see theLICENSE file for details.
AtGoatBytes.IO, our mission is to develop secure software solutions that empower businesses totransform the world. With a focus on innovation and excellence, we strive to deliver cutting-edgeproducts that meet the evolving needs of businesses across various industries.
About
Kotlin Reflection Library