XAnnotation.kt

/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.room.compiler.processing

/**
 * This wraps annotations that may be declared in sources, and thus not representable with a
 * compiled type. This is an equivalent to the Java AnnotationMirror API.
 *
 * Values in the annotation can be accessed via [annotationValues], the [XAnnotation.get] extension
 * function, or any of the "getAs*" helper functions.
 *
 * In comparison, [XAnnotationBox] is used in situations where the annotation class is already
 * compiled and can be referenced. This can be converted with [asAnnotationBox] if the annotation
 * class is already compiled.
 */
interface XAnnotation {
    /**
     * The simple name of the annotation class.
     */
    val name: String

    /**
     * The fully qualified name of the annotation class.
     * Accessing this forces the type to be resolved.
     */
    val qualifiedName: String

    /**
     * The [XType] representing the annotation class.
     *
     * Accessing this requires resolving the type, and is thus more expensive that just accessing
     * [name].
     */
    val type: XType

    /**
     * All values declared in the annotation class.
     */
    val annotationValues: List<XAnnotationValue>

    /**
     * Returns the value of the given [methodName] as a type reference.
     */
    fun getAsType(methodName: String): XType = get(methodName)

    /**
     * Returns the value of the given [methodName] as a list of type references.
     */
    fun getAsTypeList(methodName: String): List<XType> = get(methodName)

    /**
     * Returns the value of the given [methodName] as another [XAnnotation].
     */
    fun getAsAnnotation(methodName: String): XAnnotation = get(methodName)

    /**
     * Returns the value of the given [methodName] as a list of [XAnnotation].
     */
    fun getAsAnnotationList(methodName: String): List<XAnnotation> = get(methodName)

    /**
     * Returns the value of the given [methodName] as a [XEnumEntry].
     */
    fun getAsEnum(methodName: String): XEnumEntry = get(methodName)

    /**
     * Returns the value of the given [methodName] as a list of [XEnumEntry].
     */
    fun getAsEnumList(methodName: String): List<XEnumEntry> = get(methodName)
}

/**
 * Returns the value of the given [methodName], throwing an exception if the method is not
 * found or if the given type [T] does not match the actual type.
 *
 * Note that non primitive types are wrapped by interfaces in order to allow them to be
 * represented by the process:
 * - "Class" types are represented with [XType]
 * - Annotations are represented with [XAnnotation]
 * - Enums are represented with [XEnumEntry]
 *
 * For convenience, wrapper functions are provided for these types, eg [XAnnotation.getAsType]
 */
inline fun <reified T> XAnnotation.get(methodName: String): T {
    val argument = annotationValues.firstOrNull { it.name == methodName }
        ?: error("No property named $methodName was found in annotation $name")

    return argument.value as? T ?: error(
        "Value of $methodName of type ${argument.value?.javaClass} " +
            "cannot be cast to ${T::class.java}"
    )
}

/**
 * Returns the value of the given [methodName], throwing an exception if the method is not
 * found or if the given type [T] does not match the actual type.
 *
 * This uses a non-reified type and takes in a Class so it is callable by Java users.
 *
 * Note that non primitive types are wrapped by interfaces in order to allow them to be
 * represented by the process:
 * - "Class" types are represented with [XType]
 * - Annotations are represented with [XAnnotation]
 * - Enums are represented with [XEnumEntry]
 *
 * For convenience, wrapper functions are provided for these types, eg [XAnnotation.getAsType]
 */
fun <T> XAnnotation.get(methodName: String, clazz: Class<T>): T {
    val argument = annotationValues.firstOrNull { it.name == methodName }
        ?: error("No property named $methodName was found in annotation $name")

    if (!clazz.isInstance(argument.value)) {
        error("Value of $methodName of type ${argument.value?.javaClass} cannot be cast to $clazz")
    }

    @Suppress("UNCHECKED_CAST")
    return argument.value as T
}

/**
 * Get a representation of this [XAnnotation] as a [XAnnotationBox]. This is helpful for converting
 * to [XAnnotationBox] after getting annotations with [XAnnotated.getAllAnnotations].
 *
 * Only possible if the annotation class is available (ie it is in the classpath and not in
 * the compiled sources).
 */
inline fun <reified T : Annotation> XAnnotation.asAnnotationBox(): XAnnotationBox<T> {
    return (this as InternalXAnnotation).asAnnotationBox(T::class.java)
}