Refactor functions to properties on AnnotationItem

Refactor AnnotationItem.originalName, .qualifiedName, .targets, and
.attributes from functions to properties.

Test: Existing tests pass
Change-Id: Ic5caad859523430a65b147cdc1a7ed59e67f4147
diff --git a/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt b/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt
index ea7dcb3..2b76fd9 100644
--- a/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt
+++ b/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt
@@ -178,7 +178,7 @@
 
         val annotation = method.modifiers.findAnnotation("android.support.annotation.RequiresPermission")
         if (annotation != null) {
-            for (attribute in annotation.attributes()) {
+            for (attribute in annotation.attributes) {
                 var values: List<AnnotationAttributeValue>? = null
                 when (attribute.name) {
                     "value", "allOf", "anyOf" -> {
diff --git a/src/main/java/com/android/tools/metalava/AnnotationFilter.kt b/src/main/java/com/android/tools/metalava/AnnotationFilter.kt
index 5fc0d37..172c32a 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationFilter.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationFilter.kt
@@ -45,7 +45,7 @@
     }
 
     override fun matches(annotation: AnnotationItem): Boolean {
-        if (annotation.qualifiedName() == null) {
+        if (annotation.qualifiedName == null) {
             return false
         }
         val wrapper = AnnotationFilterEntry.fromAnnotationItem(annotation)
diff --git a/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt
index f5bba25..0f4ab87 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt
@@ -86,7 +86,7 @@
                         }
                     } else {
                         // TODO: Check for other incompatibilities than nullness?
-                        val qualifiedName = annotation.qualifiedName() ?: continue
+                        val qualifiedName = annotation.qualifiedName ?: continue
                         if (newModifiers.findAnnotation(qualifiedName) == null) {
                             addAnnotation = true
                         }
diff --git a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
index 94d21b8..793cc0d 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
@@ -274,7 +274,7 @@
                     }
                 } else {
                     // TODO: Check for other incompatibilities than nullness?
-                    val qualifiedName = annotation.qualifiedName() ?: return
+                    val qualifiedName = annotation.qualifiedName ?: return
                     if (newModifiers.findAnnotation(qualifiedName) == null) {
                         addAnnotation = true
                     }
@@ -322,7 +322,7 @@
                 override fun compare(old: Item, new: Item) {
                     // Transfer any show/hide annotations from the external to the main codebase.
                     for (annotation in old.modifiers.annotations()) {
-                        val qualifiedName = annotation.qualifiedName() ?: continue
+                        val qualifiedName = annotation.qualifiedName ?: continue
                         if ((showAnnotations.matches(annotation) || hideAnnotations.matches(annotation) || hideMetaAnnotations.contains(qualifiedName)) &&
                             new.modifiers.findAnnotation(qualifiedName) == null
                         ) {
@@ -542,7 +542,7 @@
         var haveNullable = false
         var haveNotNull = false
         for (existing in item.modifiers.annotations()) {
-            val name = existing.qualifiedName() ?: continue
+            val name = existing.qualifiedName ?: continue
             if (isNonNull(name)) {
                 haveNotNull = true
             }
@@ -804,14 +804,10 @@
 // TODO: Replace with usage of DefaultAnnotationAttribute?
 class XmlBackedAnnotationItem(
     codebase: Codebase,
-    private val qualifiedName: String,
-    private val attributes: List<XmlBackedAnnotationAttribute> = emptyList()
+    override val originalName: String,
+    override val attributes: List<XmlBackedAnnotationAttribute> = emptyList()
 ) : DefaultAnnotationItem(codebase) {
-
-    override fun originalName(): String? = qualifiedName
-    override fun qualifiedName(): String? = AnnotationItem.mapName(codebase, qualifiedName)
-
-    override fun attributes() = attributes
+    override val qualifiedName: String? = AnnotationItem.mapName(codebase, originalName)
 
     override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
         val qualifiedName = AnnotationItem.mapName(codebase, qualifiedName, null, target) ?: return ""
diff --git a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
index d82139d..98b1b5c 100644
--- a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
@@ -30,9 +30,6 @@
 import com.android.tools.metalava.model.VisibilityLevel
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import com.android.tools.metalava.model.visitors.ItemVisitor
-import java.util.ArrayList
-import java.util.HashMap
-import java.util.HashSet
 import java.util.Locale
 import java.util.function.Predicate
 
@@ -695,7 +692,7 @@
 
         if (annotation != null) {
             hasAnnotation = true
-            for (attribute in annotation.attributes()) {
+            for (attribute in annotation.attributes) {
                 var values: List<AnnotationAttributeValue>? = null
                 var any = false
                 when (attribute.name) {
@@ -804,7 +801,7 @@
                     val annotationName = (
                         item.modifiers.annotations().firstOrNull { annotation ->
                             options.showAnnotations.matches(annotation)
-                        }?.qualifiedName() ?: options.showAnnotations.firstQualifiedName()
+                        }?.qualifiedName ?: options.showAnnotations.firstQualifiedName()
                         ).removePrefix(ANDROID_ANNOTATION_PREFIX)
                     reporter.report(
                         Issues.UNHIDDEN_SYSTEM_API, item,
diff --git a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
index 4f830c5..f9be836 100644
--- a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
+++ b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
@@ -30,7 +30,6 @@
 import com.android.tools.metalava.model.VisitCandidate
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import com.intellij.util.containers.Stack
-import java.util.Comparator
 import java.util.function.Predicate
 
 /**
@@ -452,7 +451,7 @@
                         item1.parameterIndex.compareTo((item2 as ParameterItem).parameterIndex)
                     }
                     is AnnotationItem -> {
-                        (item1.qualifiedName() ?: "").compareTo((item2 as AnnotationItem).qualifiedName() ?: "")
+                        (item1.qualifiedName ?: "").compareTo((item2 as AnnotationItem).qualifiedName ?: "")
                     }
                     is PropertyItem -> {
                         item1.name().compareTo((item2 as PropertyItem).name())
diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
index b43209b..1cd80e4 100644
--- a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
@@ -147,7 +147,7 @@
             private fun findThreadAnnotations(annotations: List<AnnotationItem>): List<String> {
                 var result: MutableList<String>? = null
                 for (annotation in annotations) {
-                    val name = annotation.qualifiedName()
+                    val name = annotation.qualifiedName
                     if (name != null && name.endsWith("Thread") &&
                         (
                             name.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX) ||
@@ -184,7 +184,7 @@
                 depth: Int,
                 visitedClasses: MutableSet<String> = mutableSetOf()
             ) {
-                val name = annotation.qualifiedName()
+                val name = annotation.qualifiedName
                 if (name == null || name.startsWith(JAVA_LANG_PREFIX)) {
                     // Ignore java.lang.Retention etc.
                     return
@@ -226,7 +226,7 @@
                             "Unbounded recursion, processing annotation ${annotation.toSource()} " +
                                 "in $item in ${item.sourceFile()} "
                         )
-                    } else if (nested.qualifiedName() !in visitedClasses) {
+                    } else if (nested.qualifiedName !in visitedClasses) {
                         handleAnnotation(nested, item, depth + 1, visitedClasses)
                     }
                 }
@@ -284,7 +284,7 @@
                 var values: List<AnnotationAttributeValue>? = null
                 var any = false
                 var conditional = false
-                for (attribute in annotation.attributes()) {
+                for (attribute in annotation.attributes) {
                     when (attribute.name) {
                         "value", "allOf" -> {
                             values = attribute.leafValues()
diff --git a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
index d53af19..48178dc 100644
--- a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
+++ b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
@@ -186,7 +186,7 @@
     /** For a given item, extract the relevant annotations for that item */
     private fun checkItem(item: Item) {
         for (annotation in item.modifiers.annotations()) {
-            val qualifiedName = annotation.qualifiedName() ?: continue
+            val qualifiedName = annotation.qualifiedName ?: continue
             if (qualifiedName.startsWith(JAVA_LANG_PREFIX) ||
                 qualifiedName.startsWith(ANDROIDX_ANNOTATION_PREFIX) ||
                 qualifiedName.startsWith(ANDROID_ANNOTATION_PREFIX) ||
@@ -195,7 +195,7 @@
                 if (annotation.isTypeDefAnnotation()) {
                     // Imported typedef
                     addItem(item, AnnotationHolder(null, annotation, null))
-                } else if (annotation.targets().contains(AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE) &&
+                } else if (annotation.targets.contains(AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE) &&
                     !options.includeSourceRetentionAnnotations
                 ) {
                     addItem(item, AnnotationHolder(null, annotation, null))
@@ -481,7 +481,7 @@
                     JavaUAnnotation.wrap(annotationItem.psiAnnotation)
                 else -> return
             }
-        val qualifiedName = annotationItem.qualifiedName()
+        val qualifiedName = annotationItem.qualifiedName
 
         writer.mark()
         writer.print("    <annotation name=\"")
diff --git a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
index fb9feee..0460673 100644
--- a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
+++ b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
@@ -102,7 +102,7 @@
                 val annotation = method.modifiers.findAnnotation("kotlin.jvm.Throws")
                 if (annotation != null) {
                     // There can be multiple values
-                    for (attribute in annotation.attributes()) {
+                    for (attribute in annotation.attributes) {
                         for (v in attribute.leafValues()) {
                             val source = v.toSource()
                             if (source.endsWith(exception.simpleName() + "::class")) {
diff --git a/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt b/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt
index ee650a7..a7b5df0 100644
--- a/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt
+++ b/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt
@@ -134,7 +134,7 @@
     }
 
     private fun isNullFromTypeParam(it: AnnotationItem) =
-        it.qualifiedName()?.endsWith("NullFromTypeParam") == true
+        it.qualifiedName?.endsWith("NullFromTypeParam") == true
 
     private fun isAnyNullabilityAnnotation(it: AnnotationItem) =
         it.isNullnessAnnotation() || isNullFromTypeParam(it)
diff --git a/src/main/java/com/android/tools/metalava/Reporter.kt b/src/main/java/com/android/tools/metalava/Reporter.kt
index 33f7971..b830c61 100644
--- a/src/main/java/com/android/tools/metalava/Reporter.kt
+++ b/src/main/java/com/android/tools/metalava/Reporter.kt
@@ -188,9 +188,9 @@
         item ?: return false
 
         for (annotation in item.modifiers.annotations()) {
-            val annotationName = annotation.qualifiedName()
+            val annotationName = annotation.qualifiedName
             if (annotationName != null && annotationName in SUPPRESS_ANNOTATIONS) {
-                for (attribute in annotation.attributes()) {
+                for (attribute in annotation.attributes) {
                     // Assumption that all annotations in SUPPRESS_ANNOTATIONS only have
                     // one attribute such as value/names that is varargs of String
                     val value = attribute.value
diff --git a/src/main/java/com/android/tools/metalava/SdkFileWriter.kt b/src/main/java/com/android/tools/metalava/SdkFileWriter.kt
index 2cfc372..6d71da5 100644
--- a/src/main/java/com/android/tools/metalava/SdkFileWriter.kt
+++ b/src/main/java/com/android/tools/metalava/SdkFileWriter.kt
@@ -77,7 +77,7 @@
                 val value = field.initialValue() ?: continue
                 val annotations = field.modifiers.annotations()
                 for (annotation in annotations) {
-                    if (SDK_CONSTANT_ANNOTATION == annotation.qualifiedName()) {
+                    if (SDK_CONSTANT_ANNOTATION == annotation.qualifiedName) {
                         val resolved =
                             annotation.findAttribute(null)?.leafValues()?.firstOrNull()?.resolve() as? FieldItem
                                 ?: continue
@@ -99,11 +99,11 @@
                 val annotations = clazz.modifiers.annotations()
                 if (annotations.isNotEmpty()) {
                     for (annotation in annotations) {
-                        if (SDK_WIDGET_ANNOTATION == annotation.qualifiedName()) {
+                        if (SDK_WIDGET_ANNOTATION == annotation.qualifiedName) {
                             widgets.add(clazz)
                             annotated = true
                             break
-                        } else if (SDK_LAYOUT_ANNOTATION == annotation.qualifiedName()) {
+                        } else if (SDK_LAYOUT_ANNOTATION == annotation.qualifiedName) {
                             layouts.add(clazz)
                             annotated = true
                             break
diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
index e6cdd09..ff986f6 100644
--- a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
@@ -59,10 +59,10 @@
     val codebase: Codebase
 
     /** Fully qualified name of the annotation */
-    fun qualifiedName(): String?
+    val qualifiedName: String?
 
     /** Fully qualified name of the annotation (prior to name mapping) */
-    fun originalName(): String?
+    val originalName: String?
 
     /** Generates source code for this annotation (using fully qualified names) */
     fun toSource(
@@ -71,10 +71,10 @@
     ): String
 
     /** The applicable targets for this annotation */
-    fun targets(): Set<AnnotationTarget>
+    val targets: Set<AnnotationTarget>
 
     /** Attributes of the annotation (may be empty) */
-    fun attributes(): List<AnnotationAttribute>
+    val attributes: List<AnnotationAttribute>
 
     /** True if this annotation represents @Nullable or @NonNull (or some synonymous annotation) */
     fun isNullnessAnnotation(): Boolean {
@@ -83,17 +83,17 @@
 
     /** True if this annotation represents @Nullable (or some synonymous annotation) */
     fun isNullable(): Boolean {
-        return isNullableAnnotation(qualifiedName() ?: return false)
+        return isNullableAnnotation(qualifiedName ?: return false)
     }
 
     /** True if this annotation represents @NonNull (or some synonymous annotation) */
     fun isNonNull(): Boolean {
-        return isNonNullAnnotation(qualifiedName() ?: return false)
+        return isNonNullAnnotation(qualifiedName ?: return false)
     }
 
     /** True if this annotation represents @IntDef, @LongDef or @StringDef */
     fun isTypeDefAnnotation(): Boolean {
-        val name = qualifiedName() ?: return false
+        val name = qualifiedName ?: return false
         if (!(name.endsWith("Def"))) {
             return false
         }
@@ -112,7 +112,7 @@
      * The parameter name should be the default attribute or "value".
      */
     fun isParameterName(): Boolean {
-        return qualifiedName()?.endsWith(".ParameterName") ?: return false
+        return qualifiedName?.endsWith(".ParameterName") ?: return false
     }
 
     /**
@@ -120,30 +120,30 @@
      * The default value should be the default attribute or "value".
      */
     fun isDefaultValue(): Boolean {
-        return qualifiedName()?.endsWith(".DefaultValue") ?: return false
+        return qualifiedName?.endsWith(".DefaultValue") ?: return false
     }
 
     /** Returns the given named attribute if specified */
     fun findAttribute(name: String?): AnnotationAttribute? {
         val actualName = name ?: ATTR_VALUE
-        return attributes().firstOrNull { it.name == actualName }
+        return attributes.firstOrNull { it.name == actualName }
     }
 
     /** Find the class declaration for the given annotation */
     fun resolve(): ClassItem? {
-        return codebase.findClass(qualifiedName() ?: return null)
+        return codebase.findClass(qualifiedName ?: return null)
     }
 
     /** If this annotation has a typedef annotation associated with it, return it */
     fun findTypedefAnnotation(): AnnotationItem? {
-        val className = originalName() ?: return null
+        val className = originalName ?: return null
         return codebase.findClass(className)?.modifiers?.annotations()?.firstOrNull { it.isTypeDefAnnotation() }
     }
 
     /** Returns the retention of this annotation */
     val retention: AnnotationRetention
         get() {
-            val name = qualifiedName()
+            val name = qualifiedName
             if (name != null) {
                 val cls = codebase.findClass(name) ?: (codebase as? PsiBasedCodebase)?.findOrCreateClass(name)
                 if (cls != null) {
@@ -159,8 +159,7 @@
     companion object {
         /** The simple name of an annotation, which is the annotation name (not qualified name) prefixed by @ */
         fun simpleName(item: AnnotationItem): String {
-            val qualifiedName = item.qualifiedName() ?: return ""
-            return "@${qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1)}"
+            return item.qualifiedName?.let { "@${it.substringAfterLast('.')}" }.orEmpty()
         }
 
         /**
@@ -417,7 +416,7 @@
             annotation: AnnotationItem,
             classFinder: (String) -> ClassItem?
         ): Set<AnnotationTarget> {
-            val qualifiedName = annotation.qualifiedName() ?: return NO_ANNOTATION_TARGETS
+            val qualifiedName = annotation.qualifiedName ?: return NO_ANNOTATION_TARGETS
             if (options.passThroughAnnotations.contains(qualifiedName)) {
                 return ANNOTATION_IN_ALL_STUBS
             }
@@ -677,15 +676,8 @@
 
 /** Default implementation of an annotation item */
 abstract class DefaultAnnotationItem(override val codebase: Codebase) : AnnotationItem {
-    protected var targets: Set<AnnotationTarget>? = null
-
-    override fun targets(): Set<AnnotationTarget> {
-        if (targets == null) {
-            targets = AnnotationItem.computeTargets(this) { className ->
-                codebase.findClass(className)
-            }
-        }
-        return targets!!
+    override val targets: Set<AnnotationTarget> by lazy {
+        AnnotationItem.computeTargets(this, codebase::findClass)
     }
 }
 
diff --git a/src/main/java/com/android/tools/metalava/model/ModifierList.kt b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
index 2f27cf2..e589401 100644
--- a/src/main/java/com/android/tools/metalava/model/ModifierList.kt
+++ b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
@@ -166,7 +166,7 @@
             return false
         }
         return annotations().any { annotation ->
-            options.hideMetaAnnotations.contains(annotation.qualifiedName())
+            options.hideMetaAnnotations.contains(annotation.qualifiedName)
         }
     }
 
@@ -179,7 +179,7 @@
     fun findAnnotation(qualifiedName: String): AnnotationItem? {
         val mappedName = AnnotationItem.mapName(codebase, qualifiedName)
         return annotations().firstOrNull {
-            mappedName == it.qualifiedName()
+            mappedName == it.qualifiedName
         }
     }
 
@@ -433,7 +433,7 @@
 
             // Ensure stable signature file order
             if (annotations.size > 1) {
-                annotations = annotations.sortedBy { it.qualifiedName() }
+                annotations = annotations.sortedBy { it.qualifiedName }
             }
 
             if (annotations.isNotEmpty()) {
@@ -446,13 +446,13 @@
                     }
 
                     var printAnnotation = annotation
-                    if (!annotation.targets().contains(target)) {
+                    if (!annotation.targets.contains(target)) {
                         continue
                     } else if ((annotation.isNullnessAnnotation())) {
                         if (skipNullnessAnnotations) {
                             continue
                         }
-                    } else if (annotation.qualifiedName() == "java.lang.Deprecated") {
+                    } else if (annotation.qualifiedName == "java.lang.Deprecated") {
                         // Special cased in stubs and signature files: emitted first
                         continue
                     } else if (options.typedefMode == Options.TypedefMode.INLINE) {
@@ -461,12 +461,12 @@
                             printAnnotation = typedef
                         }
                     } else if (options.typedefMode == Options.TypedefMode.REFERENCE &&
-                        annotation.targets() === ANNOTATION_SIGNATURE_ONLY &&
+                        annotation.targets === ANNOTATION_SIGNATURE_ONLY &&
                         annotation.findTypedefAnnotation() != null
                     ) {
                         // For annotation references, only include the simple name
                         writer.write("@")
-                        writer.write(annotation.resolve()?.simpleName() ?: annotation.qualifiedName()!!)
+                        writer.write(annotation.resolve()?.simpleName() ?: annotation.qualifiedName!!)
                         if (separateLines) {
                             writer.write("\n")
                         } else {
@@ -477,11 +477,11 @@
 
                     // Optionally filter out duplicates
                     if (index > 0 && filterDuplicates) {
-                        val qualifiedName = annotation.qualifiedName()
+                        val qualifiedName = annotation.qualifiedName
                         var found = false
                         for (i in 0 until index) {
                             val prev = annotations[i]
-                            if (prev.qualifiedName() == qualifiedName) {
+                            if (prev.qualifiedName == qualifiedName) {
                                 found = true
                                 break
                             }
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
index c8e2d75..567a935 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
@@ -48,13 +48,9 @@
 class PsiAnnotationItem private constructor(
     override val codebase: PsiBasedCodebase,
     val psiAnnotation: PsiAnnotation,
-    private val originalName: String?
+    override val originalName: String?
 ) : DefaultAnnotationItem(codebase) {
-    private val qualifiedName = AnnotationItem.mapName(codebase, originalName)
-
-    private var attributes: List<AnnotationAttribute>? = null
-
-    override fun originalName(): String? = originalName
+    override val qualifiedName: String? = AnnotationItem.mapName(codebase, originalName)
 
     override fun toString(): String = toSource()
 
@@ -78,37 +74,16 @@
         return super.isNonNull()
     }
 
-    override fun qualifiedName() = qualifiedName
-
-    override fun attributes(): List<AnnotationAttribute> {
-        if (attributes == null) {
-            val psiAttributes = psiAnnotation.parameterList.attributes
-            attributes = if (psiAttributes.isEmpty()) {
-                emptyList()
-            } else {
-                val list = mutableListOf<AnnotationAttribute>()
-                for (parameter in psiAttributes) {
-                    list.add(
-                        PsiAnnotationAttribute(
-                            codebase,
-                            parameter.name ?: ATTR_VALUE, parameter.value ?: continue
-                        )
-                    )
-                }
-                list
+    override val attributes: List<PsiAnnotationAttribute> by lazy {
+        psiAnnotation.parameterList.attributes.mapNotNull { attribute ->
+            attribute.value?.let { value ->
+                PsiAnnotationAttribute(codebase, attribute.name ?: ATTR_VALUE, value)
             }
-        }
-
-        return attributes!!
+        }.toList()
     }
 
-    override fun targets(): Set<AnnotationTarget> {
-        if (targets == null) {
-            targets = AnnotationItem.computeTargets(this) { className ->
-                codebase.findOrCreateClass(className)
-            }
-        }
-        return targets!!
+    override val targets: Set<AnnotationTarget> by lazy {
+        AnnotationItem.computeTargets(this, codebase::findOrCreateClass)
     }
 
     companion object {
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
index 8a5b130..bff9155 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
@@ -68,7 +68,7 @@
             // Java: Look for @ParameterName annotation
             val annotation = modifiers.annotations().firstOrNull { it.isParameterName() }
             if (annotation != null) {
-                return annotation.attributes().firstOrNull()?.value?.value()?.toString()
+                return annotation.attributes.firstOrNull()?.value?.value()?.toString()
             }
         }
 
@@ -166,7 +166,7 @@
             // Java: Look for @ParameterName annotation
             val annotation = modifiers.annotations().firstOrNull { it.isDefaultValue() }
             if (annotation != null) {
-                return annotation.attributes().firstOrNull()?.value?.value()?.toString()
+                return annotation.attributes.firstOrNull()?.value?.value()?.toString()
             }
         }
 
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiTypePrinter.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiTypePrinter.kt
index 7648421..0b99b35 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiTypePrinter.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiTypePrinter.kt
@@ -521,7 +521,7 @@
 
         if (elementAnnotations != null) {
             for (annotation in elementAnnotations) {
-                val name = mapAnnotation(annotation.qualifiedName())
+                val name = mapAnnotation(annotation.qualifiedName)
                 if (name != null) {
                     sb.append(annotation.toSource()).append(' ')
                     updated = true
diff --git a/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt
index 6598365..8474daa 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt
@@ -16,7 +16,7 @@
 
 package com.android.tools.metalava.model.psi
 
-import com.android.SdkConstants
+import com.android.SdkConstants.ATTR_VALUE
 import com.android.tools.lint.detector.api.ConstantEvaluator
 import com.android.tools.metalava.model.AnnotationArrayAttributeValue
 import com.android.tools.metalava.model.AnnotationAttribute
@@ -47,13 +47,9 @@
 class UAnnotationItem private constructor(
     override val codebase: PsiBasedCodebase,
     val uAnnotation: UAnnotation,
-    private val originalName: String?
+    override val originalName: String?
 ) : DefaultAnnotationItem(codebase) {
-    private val qualifiedName = AnnotationItem.mapName(codebase, originalName)
-
-    private var attributes: List<AnnotationAttribute>? = null
-
-    override fun originalName(): String? = originalName
+    override val qualifiedName: String? = AnnotationItem.mapName(codebase, originalName)
 
     override fun toString(): String = toSource()
 
@@ -77,37 +73,14 @@
         return super.isNonNull()
     }
 
-    override fun qualifiedName() = qualifiedName
-
-    override fun attributes(): List<AnnotationAttribute> {
-        if (attributes == null) {
-            val uAttributes = uAnnotation.attributeValues
-            attributes = if (uAttributes.isEmpty()) {
-                emptyList()
-            } else {
-                val list = mutableListOf<AnnotationAttribute>()
-                for (parameter in uAttributes) {
-                    list.add(
-                        UAnnotationAttribute(
-                            codebase,
-                            parameter.name ?: SdkConstants.ATTR_VALUE, parameter.expression
-                        )
-                    )
-                }
-                list
-            }
-        }
-
-        return attributes!!
+    override val attributes: List<UAnnotationAttribute> by lazy {
+        uAnnotation.attributeValues.map { attribute ->
+            UAnnotationAttribute(codebase, attribute.name ?: ATTR_VALUE, attribute.expression)
+        }.toList()
     }
 
-    override fun targets(): Set<AnnotationTarget> {
-        if (targets == null) {
-            targets = AnnotationItem.computeTargets(this) { className ->
-                codebase.findOrCreateClass(className)
-            }
-        }
-        return targets!!
+    override val targets: Set<AnnotationTarget> by lazy {
+        AnnotationItem.computeTargets(this, codebase::findOrCreateClass)
     }
 
     companion object {
@@ -157,7 +130,7 @@
             sb.append("@")
             sb.append(qualifiedName)
             sb.append("(")
-            if (attributes.size == 1 && (attributes[0].first == null || attributes[0].first == SdkConstants.ATTR_VALUE)) {
+            if (attributes.size == 1 && (attributes[0].first == null || attributes[0].first == ATTR_VALUE)) {
                 // Special case: omit "value" if it's the only attribute
                 appendValue(codebase, sb, attributes[0].second, target, showDefaultAttrs)
             } else {
@@ -168,7 +141,7 @@
                     } else {
                         sb.append(", ")
                     }
-                    sb.append(attribute.first ?: SdkConstants.ATTR_VALUE)
+                    sb.append(attribute.first ?: ATTR_VALUE)
                     sb.append('=')
                     appendValue(codebase, sb, attribute.second, target, showDefaultAttrs)
                 }
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt
index e741a80..fce3a9d 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt
@@ -28,10 +28,10 @@
     source: String,
     mapName: Boolean = true
 ) : DefaultAnnotationItem(codebase) {
-    private val originalName: String
-    private val qualifiedName: String?
+    override val originalName: String
+    override val qualifiedName: String?
     private val full: String
-    private val attributes: List<AnnotationAttribute>
+    override val attributes: List<AnnotationAttribute>
 
     init {
         val index = source.indexOf("(")
@@ -56,8 +56,5 @@
         }
     }
 
-    override fun originalName(): String? = originalName
-    override fun qualifiedName(): String? = qualifiedName
-    override fun attributes(): List<AnnotationAttribute> = attributes
     override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String = full
 }
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt b/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt
index a48976c..2c72a0d 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt
@@ -68,9 +68,9 @@
                 }
             val codebase = codebase
             val item = object : DefaultAnnotationItem(codebase) {
-                override fun attributes(): List<AnnotationAttribute> = attributes
-                override fun originalName(): String? = originalName
-                override fun qualifiedName(): String? = qualifiedName
+                override val attributes: List<AnnotationAttribute> = attributes
+                override val originalName: String? = originalName
+                override val qualifiedName: String? = qualifiedName
                 override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String = source
             }
             annotations.add(item)
diff --git a/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
index c466ebb..c9b3bff 100644
--- a/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
@@ -42,8 +42,8 @@
             "@androidx.annotation.Nullable"
         )
         assertEquals("@androidx.annotation.Nullable", annotation.toSource())
-        assertEquals("androidx.annotation.Nullable", annotation.qualifiedName())
-        assertTrue(annotation.attributes().isEmpty())
+        assertEquals("androidx.annotation.Nullable", annotation.qualifiedName)
+        assertTrue(annotation.attributes.isEmpty())
     }
 
     @Test
@@ -53,8 +53,8 @@
             "@androidx.annotation.IntRange(from = 20, to = 40)"
         )
         assertEquals("@androidx.annotation.IntRange(from = 20, to = 40)", annotation.toSource())
-        assertEquals("androidx.annotation.IntRange", annotation.qualifiedName())
-        assertEquals(2, annotation.attributes().size)
+        assertEquals("androidx.annotation.IntRange", annotation.qualifiedName)
+        assertEquals(2, annotation.attributes.size)
         assertEquals("from", annotation.findAttribute("from")?.name)
         assertEquals("20", annotation.findAttribute("from")?.value.toString())
         assertEquals("to", annotation.findAttribute("to")?.name)
@@ -71,8 +71,8 @@
             "@androidx.annotation.IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})",
             annotation.toSource()
         )
-        assertEquals("androidx.annotation.IntDef", annotation.qualifiedName())
-        assertEquals(1, annotation.attributes().size)
+        assertEquals("androidx.annotation.IntDef", annotation.qualifiedName)
+        assertEquals(1, annotation.attributes.size)
         val attribute = annotation.findAttribute("value")
         assertNotNull(attribute)
         assertEquals("value", attribute?.name)