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

import com.squareup.javapoet.ClassName

/**
 * 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

    /**
     * The [XTypeElement] representing the annotation class.
     *
     * Accessing this requires resolving the type, and is thus more expensive that just accessing
     * [name].
     */
    val typeElement: XTypeElement
        // All annotations are represented by XTypeElements, so this should always be non-null.
        get() = requireNotNull(type.typeElement)

    /**
     * The [ClassName] representing the annotation class.
     *
     * Accessing this requires resolving the type, and is thus more expensive that just accessing
     * [name].
     */
    val className: ClassName
        get() = typeElement.asClassName().java

    /** 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 = getAnnotationValue(methodName).asType()

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

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

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

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

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

    /** Returns the value of the given [methodName] as a [Boolean]. */
    fun getAsBoolean(methodName: String): Boolean = getAnnotationValue(methodName).asBoolean()

    /** Returns the value of the given [methodName] as a list of [Boolean]. */
    fun getAsBooleanList(methodName: String): List<Boolean> =
        getAnnotationValue(methodName).asBooleanList()

    /** Returns the value of the given [methodName] as a [String]. */
    fun getAsString(methodName: String): String = getAnnotationValue(methodName).asString()

    /** Returns the value of the given [methodName] as a list of [String]. */
    fun getAsStringList(methodName: String): List<String> =
        getAnnotationValue(methodName).asStringList()

    /** Returns the value of the given [methodName] as a [Int]. */
    fun getAsInt(methodName: String): Int = getAnnotationValue(methodName).asInt()

    /** Returns the value of the given [methodName] as a list of [Int]. */
    fun getAsIntList(methodName: String): List<Int> = getAnnotationValue(methodName).asIntList()

    /** Returns the value of the given [methodName] as a [Long]. */
    fun getAsLong(methodName: String): Long = getAnnotationValue(methodName).asLong()

    /** Returns the value of the given [methodName] as a list of [Long]. */
    fun getAsLongList(methodName: String): List<Long> = getAnnotationValue(methodName).asLongList()

    /** Returns the value of the given [methodName] as a [Short]. */
    fun getAsShort(methodName: String): Short = getAnnotationValue(methodName).asShort()

    /** Returns the value of the given [methodName] as a list of [Short]. */
    fun getAsShortList(methodName: String): List<Short> =
        getAnnotationValue(methodName).asShortList()

    /** Returns the value of the given [methodName] as a [Float]. */
    fun getAsFloat(methodName: String): Float = getAnnotationValue(methodName).asFloat()

    /** Returns the value of the given [methodName] as a list of [Float]. */
    fun getAsFloatList(methodName: String): List<Float> =
        getAnnotationValue(methodName).asFloatList()

    /** Returns the value of the given [methodName] as a [Double]. */
    fun getAsDouble(methodName: String): Double = getAnnotationValue(methodName).asDouble()

    /** Returns the value of the given [methodName] as a list of [Double]. */
    fun getAsDoubleList(methodName: String): List<Double> =
        getAnnotationValue(methodName).asDoubleList()

    /** Returns the value of the given [methodName] as a [Byte]. */
    fun getAsByte(methodName: String): Byte = getAnnotationValue(methodName).asByte()

    /** Returns the value of the given [methodName] as a list of [Byte]. */
    fun getAsByteList(methodName: String): List<Byte> = getAnnotationValue(methodName).asByteList()

    /** Returns the value of the given [methodName] as a list of [Byte]. */
    fun getAsAnnotationValueList(methodName: String): List<XAnnotationValue> =
        getAnnotationValue(methodName).asAnnotationValueList()

    /** Returns the value of the given [methodName] as a [XAnnotationValue]. */
    fun getAnnotationValue(methodName: String): XAnnotationValue
}

/**
 * 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]
 */
// TODO: Consider deprecating this method for getAs*() methods
@Suppress("DEPRECATION")
inline fun <reified T> XAnnotation.get(methodName: String): T = get(methodName, 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]
 */
@Deprecated("Use one of the getAs*() methods instead, e.g. getAsBoolean().")
fun <T> XAnnotation.get(methodName: String, clazz: Class<T>): T {
    val argument = getAnnotationValue(methodName)

    val value = if (argument.hasListValue()) {
        // If the argument is for a list, unwrap each item in the list
        argument.asAnnotationValueList().map { it.value }
    } else {
        argument.value
    }

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

    @Suppress("UNCHECKED_CAST")
    return 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)
}