1 /*
<lambda>null2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.tools.metalava.model
18
19 import com.android.tools.metalava.model.api.flags.ApiFlag
20 import com.android.tools.metalava.model.api.flags.ApiFlags
21 import com.android.tools.metalava.reporter.FileLocation
22 import kotlin.reflect.KClass
23
24 fun isNullnessAnnotation(qualifiedName: String): Boolean =
25 isNullableAnnotation(qualifiedName) || isNonNullAnnotation(qualifiedName)
26
27 fun isNullableAnnotation(qualifiedName: String): Boolean {
28 return qualifiedName == "Nullable" ||
29 qualifiedName.endsWith(".RecentlyNullable") ||
30 qualifiedName.endsWith(".Nullable") ||
31 qualifiedName.endsWith(".NullableType")
32 }
33
isNonNullAnnotationnull34 fun isNonNullAnnotation(qualifiedName: String): Boolean {
35 return qualifiedName == "NonNull" ||
36 qualifiedName.endsWith(".RecentlyNonNull") ||
37 qualifiedName.endsWith(".NonNull") ||
38 qualifiedName.endsWith(".NotNull") ||
39 qualifiedName.endsWith(".Nonnull")
40 }
41
isJvmSyntheticAnnotationnull42 fun isJvmSyntheticAnnotation(qualifiedName: String): Boolean {
43 return qualifiedName == "kotlin.jvm.JvmSynthetic"
44 }
45
46 sealed interface AnnotationItem {
47 val annotationContext: AnnotationContext
48
49 /**
50 * The location of this annotation with the source file.
51 *
52 * Will be [FileLocation.UNKNOWN] if the location cannot be determined, e.g. because it is from
53 * a `.class` file.
54 */
55 val fileLocation: FileLocation
56
57 /** Fully qualified name of the annotation */
58 val qualifiedName: String
59
60 /**
61 * Determines the effect that this will have on whether an item annotated with this annotation
62 * will be shown as part of the API or not.
63 */
64 val showability: Showability
65
66 /**
67 * The [ApiFlag] referenced by this [AnnotationItem].
68 *
69 * This will be `null` if no [ApiFlags] have been provided or this [AnnotationItem]'s type is
70 * not [ANDROID_FLAGGED_API]. Otherwise, it will be one of the instances of [ApiFlag], e.g.
71 * [ApiFlag.REVERT_FLAGGED_API].
72 */
73 val apiFlag: ApiFlag?
74
75 /** Generates source code for this annotation (using fully qualified names) */
toSourcenull76 fun toSource(
77 target: AnnotationTarget = AnnotationTarget.SIGNATURE_FILE,
78 showDefaultAttrs: Boolean = true
79 ): String
80
81 /** The applicable targets for this annotation */
82 val targets: Set<AnnotationTarget>
83
84 /** Attributes of the annotation; may be empty. */
85 val attributes: List<AnnotationAttribute>
86
87 /**
88 * The [TypeNullability] associated with this or `null` if this is not a nullability annotation.
89 */
90 val typeNullability: TypeNullability?
91
92 /** True if this annotation represents @Nullable or @NonNull (or some synonymous annotation) */
93 fun isNullnessAnnotation(): Boolean
94
95 /** True if this annotation represents @Nullable (or some synonymous annotation) */
96 fun isNullable(): Boolean
97
98 /** True if this annotation represents @NonNull (or some synonymous annotation) */
99 fun isNonNull(): Boolean
100
101 /** True if this annotation represents @Retention (either the Java or Kotlin version) */
102 fun isRetention(): Boolean = isRetention(qualifiedName)
103
104 /** True if this annotation represents @JvmSynthetic */
105 fun isJvmSynthetic(): Boolean {
106 return isJvmSyntheticAnnotation(qualifiedName)
107 }
108
109 /** True if this annotation represents @IntDef, @LongDef or @StringDef */
isTypeDefAnnotationnull110 fun isTypeDefAnnotation(): Boolean {
111 val name = qualifiedName
112 if (!(name.endsWith("Def"))) {
113 return false
114 }
115 return (ANDROIDX_INT_DEF == name ||
116 ANDROIDX_STRING_DEF == name ||
117 ANDROIDX_LONG_DEF == name ||
118 ANDROID_INT_DEF == name ||
119 ANDROID_STRING_DEF == name ||
120 ANDROID_LONG_DEF == name)
121 }
122
123 /** Returns the given named attribute if specified */
findAttributenull124 fun findAttribute(name: String?): AnnotationAttribute? {
125 val actualName = name ?: ANNOTATION_ATTR_VALUE
126 return attributes.firstOrNull { it.name == actualName }
127 }
128
129 /** Find the class declaration for the given annotation */
resolvenull130 fun resolve(): ClassItem?
131
132 /** If this annotation has a typedef annotation associated with it, return it */
133 fun findTypedefAnnotation(): AnnotationItem?
134
135 /**
136 * Returns true iff the annotation is a show annotation.
137 *
138 * If `true` then an item annotated with this annotation (and any contents) will be added to the
139 * API.
140 *
141 * e.g. if a class is annotated with this then it will also apply (unless overridden by a closer
142 * annotation) to all its contents like nested classes, methods, fields, constructors,
143 * properties, etc.
144 */
145 fun isShowAnnotation(): Boolean
146
147 /**
148 * Returns true iff this annotation is a show for stubs purposes annotation.
149 *
150 * If `true` then an item annotated with this annotation (and any contents) which are not
151 * annotated with another [isShowAnnotation] will be added to the stubs but not the API.
152 *
153 * e.g. if a class is annotated with this then it will also apply (unless overridden by a closer
154 * annotation) to all its contents like nested classes, methods, fields, constructors,
155 * properties, etc.
156 */
157 fun isShowForStubPurposes(): Boolean
158
159 /**
160 * Returns true iff this annotation is a hide annotation.
161 *
162 * Hide annotations can either be explicitly specified when creating the [Codebase] or they can
163 * be any annotation that is annotated with a hide meta-annotation (see [isHideMetaAnnotation]).
164 *
165 * If `true` then an item annotated with this annotation (and any contents) will be excluded
166 * from the API.
167 *
168 * e.g. if a class is annotated with this then it will also apply (unless overridden by a closer
169 * annotation) to all its contents like nested classes, methods, fields, constructors,
170 * properties, etc.
171 */
172 fun isHideAnnotation(): Boolean
173
174 fun isSuppressCompatibilityAnnotation(): Boolean
175
176 /**
177 * Returns true iff this annotation is a showability annotation, i.e. one that will affect
178 * [showability].
179 */
180 fun isShowabilityAnnotation(): Boolean
181
182 /** Returns the retention of this annotation */
183 val retention: AnnotationRetention
184 get() {
185 val cls = resolve()
186 if (cls != null) {
187 if (cls.isAnnotationType()) {
188 return cls.getRetention()
189 }
190 }
191
192 return AnnotationRetention.getDefault()
193 }
194
195 /** Take a snapshot of this [AnnotationItem] suitable for use in [Codebase]. */
snapshotnull196 fun snapshot(targetCodebase: Codebase): AnnotationItem
197
198 companion object {
199 /**
200 * The simple name of an annotation, which is the annotation name (not qualified name)
201 * prefixed by @
202 */
203 fun simpleName(item: AnnotationItem): String {
204 return item.qualifiedName.let { "@${it.substringAfterLast('.')}" }
205 }
206
207 /**
208 * Given a "full" annotation name, shortens it by removing redundant package names. This is
209 * intended to be used to reduce clutter in signature files.
210 *
211 * For example, this method will convert `@androidx.annotation.Nullable` to just
212 * `@Nullable`, and `@androidx.annotation.IntRange(from=20)` to `IntRange(from=20)`.
213 */
214 fun shortenAnnotation(source: String): String {
215 return when {
216 source == "@java.lang.Deprecated" -> "@Deprecated"
217 source.startsWith(ANDROID_ANNOTATION_PREFIX, 1) -> {
218 "@" + source.substring(ANDROID_ANNOTATION_PREFIX.length + 1)
219 }
220 source.startsWith(ANDROIDX_ANNOTATION_PREFIX, 1) -> {
221 "@" + source.substring(ANDROIDX_ANNOTATION_PREFIX.length + 1)
222 }
223 else -> source
224 }
225 }
226
227 /**
228 * Reverses the [shortenAnnotation] method. Intended for use when reading in signature files
229 * that contain shortened type references.
230 */
231 fun unshortenAnnotation(source: String): String {
232 return when {
233 source == "@Deprecated" -> "@java.lang.Deprecated"
234 // The first 4 annotations are in the android.annotation. package, not
235 // androidx.annotation
236 // Nullability annotations are written as @NonNull and @Nullable in API text files,
237 // and these should be linked no android.annotation package when generating stubs.
238 source.startsWith("@SystemService") ||
239 source.startsWith("@TargetApi") ||
240 source.startsWith("@SuppressLint") ||
241 source.startsWith("@FlaggedApi") ||
242 source.startsWith("@Nullable") ||
243 source.startsWith("@NonNull") -> "@android.annotation." + source.substring(1)
244 // If the first character of the name (after "@") is lower-case, then
245 // assume it's a package name, so no need to shorten it.
246 source.startsWith("@") && source[1].isLowerCase() -> source
247 else -> {
248 "@androidx.annotation." + source.substring(1)
249 }
250 }
251 }
252 }
253 }
254
255 /** Get the [TypeNullability] from a list of [AnnotationItem]s. */
256 val List<AnnotationItem>.typeNullability
<lambda>null257 get() = mapNotNull { it.typeNullability }.firstOrNull()
258
259 /**
260 * Get the value of the named attribute as an object of the specified type or null if the attribute
261 * could not be found.
262 *
263 * This can only be called for attributes which have a single value, it will throw an exception if
264 * called for an attribute whose value is any array type. See [getAttributeValues] instead.
265 *
266 * This supports the following types for [T]:
267 * * [String] - the attribute must be of type [String] or [Class].
268 * * [AnnotationItem] - the attribute must be of an annotation type.
269 * * [Boolean] - the attribute must be of type [Boolean].
270 * * [Byte] - the attribute must be of type [Byte].
271 * * [Char] - the attribute must be of type [Char].
272 * * [Double] - the attribute must be of type [Double].
273 * * [Float] - the attribute must be of type [Float].
274 * * [Int] - the attribute must be of type [Int].
275 * * [Long] - the attribute must be of type [Long].
276 * * [Short] - the attribute must be of type [Short].
277 *
278 * Any other types will result in a [ClassCastException].
279 */
getAttributeValuenull280 inline fun <reified T : Any> AnnotationItem.getAttributeValue(name: String): T? {
281 val value = nonInlineGetAttributeValue(T::class, name) ?: return null
282 return value as T
283 }
284
285 /**
286 * Non-inline portion of functionality needed by [getAttributeValue]; separated to reduce the cost
287 * of inlining [getAttributeValue].
288 */
289 @PublishedApi
nonInlineGetAttributeValuenull290 internal fun AnnotationItem.nonInlineGetAttributeValue(kClass: KClass<*>, name: String): Any? {
291 val attributeValue = findAttribute(name)?.legacyValue ?: return null
292 val value =
293 when (attributeValue) {
294 is AnnotationArrayAttributeValue ->
295 throw IllegalStateException("Annotation attribute is of type array")
296 else -> attributeValue.value()
297 }
298 ?: return null
299
300 return convertValue(annotationContext, kClass, value)
301 }
302
303 /**
304 * Get the values of the named attribute as a list of objects of the specified type or null if the
305 * attribute could not be found.
306 *
307 * This can be used to get the value of an attribute that is either one of the types in
308 * [getAttributeValue] (in which case this returns a list containing a single item), or an array of
309 * one of the types in [getAttributeValue] (in which case this returns a list containing all the
310 * items in the array).
311 */
getAttributeValuesnull312 inline fun <reified T : Any> AnnotationItem.getAttributeValues(name: String): List<T>? {
313 return nonInlineGetAttributeValues(T::class, name) { it as T }
314 }
315
316 /**
317 * Non-inline portion of functionality needed by [getAttributeValues]; separated to reduce the cost
318 * of inlining [getAttributeValues].
319 */
320 @PublishedApi
nonInlineGetAttributeValuesnull321 internal fun <T : Any> AnnotationItem.nonInlineGetAttributeValues(
322 kClass: KClass<*>,
323 name: String,
324 caster: (Any) -> T
325 ): List<T>? {
326 val attributeValue = findAttribute(name)?.legacyValue ?: return null
327 val values =
328 when (attributeValue) {
329 is AnnotationArrayAttributeValue -> attributeValue.values.mapNotNull { it.value() }
330 else -> listOfNotNull(attributeValue.value())
331 }
332
333 return values.mapNotNull { convertValue(annotationContext, kClass, it) }.map { caster(it) }
334 }
335
336 /**
337 * Perform some conversions to try and make [value] to be an instance of [kClass].
338 *
339 * This fixes up some known issues with [value] not corresponding to the expected type but otherwise
340 * simply returns the value it is given. It is the caller's responsibility to actually cast the
341 * returned value to the correct type.
342 */
convertValuenull343 private fun convertValue(
344 annotationContext: AnnotationContext,
345 kClass: KClass<*>,
346 value: Any
347 ): Any? {
348 // The value stored for number types is not always the same as the type of the annotation
349 // attributes. This is for a number of reasons, e.g.
350 // * In a .class file annotation values are stored in the constant pool and some number types do
351 // not have their own constant form (or their own array constant form) so are stored as
352 // instances of a wider type. They need to be converted to the correct type.
353 // * In signature files annotation values are not always stored as the narrowest type, may not
354 // have a suffix and type information may not always be available when parsing.
355 if (Number::class.java.isAssignableFrom(kClass.java)) {
356 value as Number
357 return when (kClass) {
358 // Byte does have its own constant form but when stored in an array it is stored as an
359 // int.
360 Byte::class -> value.toByte()
361 // DefaultAnnotationValue.create() always reads integers as longs.
362 Int::class -> value.toInt()
363 // DefaultAnnotationValue.create() always reads floating point as doubles.
364 Float::class -> value.toFloat()
365 // Short does not have its own constant form.
366 Short::class -> value.toShort()
367 else -> value
368 }
369 }
370
371 // TODO: Push down into the model as that is likely to be more efficient.
372 if (kClass == AnnotationItem::class) {
373 return DefaultAnnotationItem.create(annotationContext, value as String)
374 }
375
376 return value
377 }
378
379 /** Provides contextual information needed by [AnnotationItem]s. */
380 interface AnnotationContext : ClassResolver {
381 /** The manager of annotations within this context. */
382 val annotationManager: AnnotationManager
383
384 /**
385 * Creates an annotation item for the given (fully qualified) Java source.
386 *
387 * Returns `null` if the source contains an annotation that is not recognized by Metalava.
388 */
createAnnotationnull389 fun createAnnotation(
390 source: String,
391 context: Item? = null,
392 ): AnnotationItem?
393 }
394
395 /** Default implementation of an annotation item */
396 open class DefaultAnnotationItem
397 /** The primary constructor is private to force subclasses to use the secondary constructor. */
398 protected constructor(
399 override val annotationContext: AnnotationContext,
400 override val fileLocation: FileLocation,
401
402 /** Fully qualified name of the annotation (prior to name mapping) */
403 protected val originalName: String,
404
405 /** Fully qualified name of the annotation (after name mapping) */
406 final override val qualifiedName: String,
407
408 /** Possibly empty list of attributes. */
409 attributesGetter: (AnnotationItem) -> List<AnnotationAttribute>,
410 ) : AnnotationItem {
411
412 override val targets
413 get() = info.targets
414
415 final override val attributes: List<AnnotationAttribute> by
416 lazy(LazyThreadSafetyMode.NONE) { attributesGetter(this) }
417
418 /** Information that metalava has gathered about this annotation item. */
419 internal val info: AnnotationInfo by lazy {
420 annotationContext.annotationManager.getAnnotationInfo(this)
421 }
422
423 override val typeNullability: TypeNullability?
424 get() = info.typeNullability
425
426 override fun isNullnessAnnotation(): Boolean {
427 return info.typeNullability != null
428 }
429
430 override fun isNullable(): Boolean {
431 return info.typeNullability == TypeNullability.NULLABLE
432 }
433
434 override fun isNonNull(): Boolean {
435 return info.typeNullability == TypeNullability.NONNULL
436 }
437
438 override val showability: Showability
439 get() = info.showability
440
441 override val apiFlag: ApiFlag?
442 get() = info.apiFlag
443
444 override fun resolve(): ClassItem? {
445 return annotationContext.resolveClass(originalName)
446 }
447
448 /** If this annotation has a typedef annotation associated with it, return it */
449 override fun findTypedefAnnotation(): AnnotationItem? {
450 return resolve()?.modifiers?.findAnnotation(AnnotationItem::isTypeDefAnnotation)
451 }
452
453 override fun isShowAnnotation(): Boolean = info.showability.show()
454
455 override fun isShowForStubPurposes(): Boolean = info.showability.showForStubsOnly()
456
457 override fun isHideAnnotation(): Boolean = info.showability.hide()
458
459 override fun isSuppressCompatibilityAnnotation(): Boolean = info.suppressCompatibility
460
461 override fun isShowabilityAnnotation(): Boolean = info.showability != Showability.NO_EFFECT
462
463 override fun snapshot(targetCodebase: Codebase): AnnotationItem {
464 // Force the info property to be initialized which will cause the AnnotationInfo for
465 // annotations of the same class as this to be created based off this AnnotationItem and
466 // not the snapshot AnnotationItem. That is important because the AnnotationInfo
467 // properties depends on accessing information like the ApiVariantSelectors which is
468 // discarded when creating the snapshot. The snapshot AnnotationItem will retrieve the
469 // cached version of the AnnotationInfo from the AnnotationManager.
470 info
471
472 return DefaultAnnotationItem(
473 targetCodebase,
474 fileLocation,
475 originalName,
476 qualifiedName,
477 ) {
478 attributes.map { DefaultAnnotationAttribute(it.name, it.legacyValue.snapshot()) }
479 }
480 }
481
482 override fun equals(other: Any?): Boolean {
483 if (other !is AnnotationItem) return false
484 return qualifiedName == other.qualifiedName && attributes == other.attributes
485 }
486
487 override fun hashCode(): Int {
488 var result = qualifiedName.hashCode()
489 result = 31 * result + attributes.hashCode()
490 return result
491 }
492
493 override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
494 val qualifiedName =
495 annotationContext.annotationManager.normalizeOutputName(qualifiedName, target)
496 ?: return ""
497
498 return formatAnnotationItem(qualifiedName, attributes)
499 }
500
501 final override fun toString() = toSource()
502
503 companion object {
504 fun formatAnnotationItem(
505 qualifiedName: String,
506 attributes: List<AnnotationAttribute>,
507 ): String {
508 return buildString {
509 append("@")
510 append(qualifiedName)
511 if (attributes.isNotEmpty()) {
512 val suppressDefaultAnnotationAttribute = attributes.size == 1
513 append("(")
514 attributes.forEachIndexed { i, attribute ->
515 if (i != 0) {
516 append(", ")
517 }
518 if (
519 !suppressDefaultAnnotationAttribute ||
520 attribute.name != ANNOTATION_ATTR_VALUE
521 ) {
522 append(attribute.name)
523 append("=")
524 }
525 append(attribute.legacyValue)
526 }
527 append(")")
528 }
529 }
530 }
531
532 fun create(annotationContext: AnnotationContext, source: String): AnnotationItem? {
533 val index = source.indexOf("(")
534 val originalName =
535 if (index == -1) source.substring(1) // Strip @
536 else source.substring(1, index)
537
538 @Suppress("UNUSED_PARAMETER")
539 fun attributes(annotationItem: AnnotationItem): List<AnnotationAttribute> =
540 if (index == -1) {
541 emptyList()
542 } else {
543 DefaultAnnotationAttribute.createList(
544 source.substring(index + 1, source.lastIndexOf(')'))
545 )
546 }
547
548 return create(annotationContext, FileLocation.UNKNOWN, originalName, ::attributes)
549 }
550
551 fun create(
552 annotationContext: AnnotationContext,
553 originalName: String,
554 attributes: List<AnnotationAttribute> = emptyList(),
555 context: Item? = null
556 ): AnnotationItem? {
557 val source = formatAnnotationItem(originalName, attributes)
558 return annotationContext.createAnnotation(source, context)
559 }
560
561 /**
562 * Create a [DefaultAnnotationItem] by mapping the [originalName] to a [qualifiedName] by
563 * using the [annotationContext]'s [AnnotationManager.normalizeInputName].
564 */
565 fun create(
566 annotationContext: AnnotationContext,
567 fileLocation: FileLocation,
568 originalName: String,
569 attributesGetter: (AnnotationItem) -> List<AnnotationAttribute>,
570 ): AnnotationItem? {
571 val qualifiedName =
572 annotationContext.annotationManager.normalizeInputName(originalName) ?: return null
573 return DefaultAnnotationItem(
574 annotationContext = annotationContext,
575 fileLocation = fileLocation,
576 originalName = originalName,
577 qualifiedName = qualifiedName,
578 attributesGetter = attributesGetter,
579 )
580 }
581 }
582 }
583
584 /** The default annotation attribute name when no name is provided. */
585 const val ANNOTATION_ATTR_VALUE = "value"
586
587 /** An attribute of an annotation, such as "value" */
588 sealed interface AnnotationAttribute {
589 /** The name of the annotation */
590 val name: String
591
592 /**
593 * The legacy annotation value.
594 *
595 * This is called `legacy` because this an old, inconsistent representation of an attribute
596 * value that exposes implementation details. It will be replaced by a properly modelled value
597 * representation.
598 */
599 val legacyValue: AnnotationAttributeValue
600
601 /**
602 * Return all leaf values; this flattens the complication of handling
603 * {@code @SuppressLint("warning")} and {@code @SuppressLint({"warning1","warning2"})
604 */
leafValuesnull605 fun leafValues(): List<AnnotationAttributeValue> {
606 val result = mutableListOf<AnnotationAttributeValue>()
607 AnnotationAttributeValue.addValues(legacyValue, result)
608 return result
609 }
610 }
611
612 const val ANNOTATION_VALUE_FALSE = "false"
613 const val ANNOTATION_VALUE_TRUE = "true"
614
615 /** An annotation value */
616 sealed interface AnnotationAttributeValue {
617 /** Generates source code for this annotation value */
toSourcenull618 fun toSource(): String
619
620 /** The value of the annotation */
621 fun value(): Any?
622
623 /**
624 * If the annotation declaration references a field (or class etc.), return the resolved class
625 */
626 fun resolve(): Item?
627
628 /**
629 * Take a snapshot of this [AnnotationAttributeValue] suitable for use in a snapshot [Codebase].
630 */
631 fun snapshot(): AnnotationAttributeValue
632
633 companion object {
634 fun addValues(
635 value: AnnotationAttributeValue,
636 into: MutableList<AnnotationAttributeValue>
637 ) {
638 if (value is AnnotationArrayAttributeValue) {
639 for (v in value.values) {
640 addValues(v, into)
641 }
642 } else if (value is AnnotationSingleAttributeValue) {
643 into.add(value)
644 }
645 }
646 }
647 }
648
649 /** An annotation value (for a single item, not an array) */
650 sealed interface AnnotationSingleAttributeValue : AnnotationAttributeValue {
651 val value: Any?
652
valuenull653 override fun value() = value
654 }
655
656 /** An annotation value for an array of items */
657 sealed interface AnnotationArrayAttributeValue : AnnotationAttributeValue {
658 /** The annotation values */
659 val values: List<AnnotationAttributeValue>
660
661 override fun resolve(): Item? {
662 error("resolve() should not be called on an array value")
663 }
664
665 override fun value() = values.mapNotNull { it.value() }.toTypedArray()
666 }
667
668 class DefaultAnnotationAttribute(
669 override val name: String,
670 override val legacyValue: AnnotationAttributeValue
671 ) : AnnotationAttribute {
672 companion object {
createnull673 fun create(name: String, value: String): DefaultAnnotationAttribute {
674 return DefaultAnnotationAttribute(name, DefaultAnnotationValue.create(value))
675 }
676
createListnull677 fun createList(source: String): List<AnnotationAttribute> {
678 val list = mutableListOf<AnnotationAttribute>() // TODO: default size = 2
679 var begin = 0
680 var index = 0
681 val length = source.length
682 while (index < length) {
683 val c = source[index]
684 if (c == '{') {
685 index = findEnd(source, index + 1, length, '}')
686 } else if (c == '"') {
687 index = findEnd(source, index + 1, length, '"')
688 } else if (c == ',') {
689 addAttribute(list, source, begin, index)
690 index++
691 begin = index
692 continue
693 } else if (c == ' ' && index == begin) {
694 begin++
695 }
696
697 index++
698 }
699
700 if (begin < length) {
701 addAttribute(list, source, begin, length)
702 }
703
704 return list
705 }
706
findEndnull707 private fun findEnd(source: String, from: Int, to: Int, sentinel: Char): Int {
708 var i = from
709 while (i < to) {
710 val c = source[i]
711 if (c == '\\') {
712 i++
713 } else if (c == sentinel) {
714 return i
715 }
716 i++
717 }
718 return to
719 }
720
addAttributenull721 private fun addAttribute(
722 list: MutableList<AnnotationAttribute>,
723 source: String,
724 from: Int,
725 to: Int
726 ) {
727 var split = source.indexOf('=', from)
728 if (split >= to) {
729 split = -1
730 }
731 val name: String
732 val value: String
733 val valueBegin: Int
734 val valueEnd: Int
735 if (split == -1) {
736 valueBegin = from
737 valueEnd = to
738 name = "value"
739 } else {
740 name = source.substring(from, split).trim()
741 valueBegin = split + 1
742 valueEnd = to
743 }
744 value = source.substring(valueBegin, valueEnd).trim()
745 if (!value.isEmpty()) {
746 list.add(create(name, value))
747 }
748 }
749 }
750
toStringnull751 override fun toString(): String {
752 return "$name=$legacyValue"
753 }
754
equalsnull755 override fun equals(other: Any?): Boolean {
756 if (other !is AnnotationAttribute) return false
757 return name == other.name && legacyValue == other.legacyValue
758 }
759
hashCodenull760 override fun hashCode(): Int {
761 var result = name.hashCode()
762 result = 31 * result + legacyValue.hashCode()
763 return result
764 }
765 }
766
767 abstract class DefaultAnnotationValue(sourceGetter: () -> String) : AnnotationAttributeValue {
768 companion object {
createnull769 fun create(valueSource: String): DefaultAnnotationValue {
770 return if (valueSource.startsWith("{")) { // Array
771 DefaultAnnotationArrayAttributeValue(
772 { valueSource },
773 {
774 assert(valueSource.startsWith("{") && valueSource.endsWith("}")) {
775 valueSource
776 }
777 valueSource
778 .substring(1, valueSource.length - 1)
779 .split(",")
780 .map { create(it.trim()) }
781 .toList()
782 },
783 )
784 } else {
785 DefaultAnnotationSingleAttributeValue(
786 { valueSource },
787 {
788 when {
789 valueSource == ANNOTATION_VALUE_TRUE -> true
790 valueSource == ANNOTATION_VALUE_FALSE -> false
791 valueSource.startsWith("\"") -> valueSource.removeSurrounding("\"")
792 valueSource.startsWith('\'') -> valueSource.removeSurrounding("'")[0]
793 else ->
794 try {
795 if (valueSource.contains(".")) {
796 valueSource.toDouble()
797 } else {
798 valueSource.toLong()
799 }
800 } catch (e: NumberFormatException) {
801 valueSource
802 }
803 }
804 },
805 )
806 }
807 }
808 }
809
810 /** The annotation value, expressed as source code */
811 private val valueSource: String by lazy(LazyThreadSafetyMode.NONE, sourceGetter)
812
toSourcenull813 override fun toSource() = valueSource
814
815 override fun toString(): String = toSource()
816 }
817
818 open class DefaultAnnotationSingleAttributeValue(
819 sourceGetter: () -> String,
820 valueGetter: () -> Any?
821 ) : DefaultAnnotationValue(sourceGetter), AnnotationSingleAttributeValue {
822
823 override val value by lazy(LazyThreadSafetyMode.NONE, valueGetter)
824
825 override fun resolve(): Item? = null
826
827 override fun snapshot(): AnnotationSingleAttributeValue {
828 // Take a snapshot of the value and sources by immediately forcing them to be initialized
829 // from their respective getters. That way there will be no connection to the original
830 // attribute value.
831 val newValue = value
832 val newSource = toSource()
833 return DefaultAnnotationSingleAttributeValue(
834 sourceGetter = { newSource },
835 valueGetter = { newValue },
836 )
837 }
838
839 override fun equals(other: Any?): Boolean {
840 if (other !is AnnotationSingleAttributeValue) return false
841 return value == other.value
842 }
843
844 override fun hashCode(): Int {
845 return value.hashCode()
846 }
847 }
848
849 class DefaultAnnotationArrayAttributeValue(
850 sourceGetter: () -> String,
851 valuesGetter: () -> List<AnnotationAttributeValue>
852 ) : DefaultAnnotationValue(sourceGetter), AnnotationArrayAttributeValue {
853
854 override val values by lazy(LazyThreadSafetyMode.NONE, valuesGetter)
855
snapshotnull856 override fun snapshot(): AnnotationArrayAttributeValue {
857 // Take a snapshot of the values and sources by immediately forcing them to be initialized
858 // from their respective getters. That way there will be no connection to the original
859 // attribute value.
860 val newValues = values.map { it.snapshot() }
861 val newSource = toSource()
862 return DefaultAnnotationArrayAttributeValue(
863 sourceGetter = { newSource },
864 valuesGetter = { newValues },
865 )
866 }
867
equalsnull868 override fun equals(other: Any?): Boolean {
869 if (other !is AnnotationArrayAttributeValue) return false
870 return values == other.values
871 }
872
hashCodenull873 override fun hashCode(): Int {
874 return values.hashCode()
875 }
876 }
877