Skip to content

Commit

Permalink
feat: Retrieve annotations in super and interface classes
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Nov 29, 2023
1 parent f1de9b3 commit 7aeae93
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -1,35 +1,61 @@
@file:Suppress("UNCHECKED_CAST")

package app.revanced.patcher.extensions

import kotlin.reflect.KClass

internal object AnnotationExtensions {
/**
* Search for an annotation recursively.
*
* @param targetAnnotation The annotation to search for.
* @param targetAnnotationClass The annotation class to search for.
* @param searchedClasses A set of annotations that have already been searched.
* @return The annotation if found, otherwise null.
*/
fun <T : Annotation> Class<*>.findAnnotationRecursively(targetAnnotation: Class<T>): T? {
fun <T : Annotation> Class<*>.findAnnotationRecursively(
targetAnnotation: Class<T>,
searchedAnnotations: HashSet<Annotation>,
): T? {
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }

@Suppress("UNCHECKED_CAST")
if (found != null) return found as T

for (annotation in this.annotations) {
if (searchedAnnotations.contains(annotation)) continue
searchedAnnotations.add(annotation)

return annotation.annotationClass.java.findAnnotationRecursively(
targetAnnotation,
searchedAnnotations
) ?: continue
}

return null
fun <T : Annotation> Class<*>.findAnnotationRecursively(
targetAnnotationClass: Class<T>,
searchedClasses: HashSet<Annotation> = hashSetOf(),
): T? {
annotations.forEach { annotation ->
// Terminate if the annotation is already searched.
if (annotation in searchedClasses) return@forEach
searchedClasses.add(annotation)

// Terminate if the annotation is found.
if (targetAnnotationClass == annotation.annotationClass.java) return annotation as T

return annotation.annotationClass.java.findAnnotationRecursively(
targetAnnotationClass,
searchedClasses,
) ?: return@forEach
}

// Search the super class.
superclass?.findAnnotationRecursively(
targetAnnotationClass,
searchedClasses,
)?.let { return it }

// Search the interfaces.
interfaces.forEach { superClass ->
return superClass.findAnnotationRecursively(
targetAnnotationClass,
searchedClasses,
) ?: return@forEach
}

return this.findAnnotationRecursively(targetAnnotation, hashSetOf())
return null
}

/**
* Search for an annotation recursively.
*
* First the annotations, then the annotated classes super class and then it's interfaces
* are searched for the annotation recursively.
*
* @param targetAnnotation The annotation to search for.
* @return The annotation if found, otherwise null.
*/
fun <T : Annotation> KClass<*>.findAnnotationRecursively(targetAnnotation: KClass<T>) =
java.findAnnotationRecursively(targetAnnotation.java)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ object MethodFingerprintExtensions {
*/
@Deprecated(
message = "Use the property instead.",
replaceWith = ReplaceWith("this.fuzzyPatternScanMethod")
replaceWith = ReplaceWith("this.fuzzyPatternScanMethod"),
)
val MethodFingerprint.fuzzyPatternScanMethod
get() = this.fuzzyPatternScanMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ abstract class MethodFingerprint(
*
* If the annotation is not present, this property is null.
*/
val fuzzyPatternScanMethod = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class.java)
val fuzzyPatternScanMethod = this::class.findAnnotationRecursively(FuzzyPatternScanMethod::class)

/**
* Resolve a [MethodFingerprint] using the lookup map built by [initializeLookupMaps].
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/app/revanced/patcher/patch/Patch.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ package app.revanced.patcher.patch
import app.revanced.patcher.PatchClass
import app.revanced.patcher.Patcher
import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
import app.revanced.patcher.patch.options.PatchOptions
import java.io.Closeable
import kotlin.reflect.full.findAnnotation
import app.revanced.patcher.patch.annotation.Patch as PatchAnnotation

/**
* A ReVanced patch.
Expand Down Expand Up @@ -60,7 +61,7 @@ sealed class Patch<out T : Context<*>> {
val options = PatchOptions()

init {
this::class.findAnnotation<app.revanced.patcher.patch.annotation.Patch>()?.let { annotation ->
this::class.findAnnotationRecursively(PatchAnnotation::class)?.let { annotation ->
name = annotation.name.ifEmpty { null }
description = annotation.description.ifEmpty { null }
compatiblePackages =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package app.revanced.patcher.extensions

import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertNull

private object AnnotationExtensionsTest {
@Test
fun `find annotation in annotated class`() {
assertNotNull(TestClasses.Annotation2::class.findAnnotationRecursively(TestClasses.Annotation::class))
}

@Test
fun `find annotation`() {
assertNotNull(TestClasses.AnnotatedClass::class.findAnnotationRecursively(TestClasses.Annotation::class))
}

@Test
fun `find annotation recursively in super class`() {
assertNotNull(TestClasses.AnnotatedClass2::class.findAnnotationRecursively(TestClasses.Annotation::class))
}

@Test
fun `find annotation recursively in super class with annotation`() {
assertNotNull(TestClasses.AnnotatedTestClass3::class.findAnnotationRecursively(TestClasses.Annotation::class))
}

@Test
fun `don't find unknown annotation in annotated class`() {
assertNull(TestClasses.AnnotatedClass::class.findAnnotationRecursively(TestClasses.UnknownAnnotation::class))
}

object TestClasses {
annotation class Annotation

@Annotation
annotation class Annotation2

annotation class UnknownAnnotation

@Annotation
abstract class AnnotatedClass

@Annotation2
class AnnotatedTestClass3

abstract class AnnotatedClass2 : AnnotatedClass()
}
}

0 comments on commit 7aeae93

Please sign in to comment.