1 /*
2 * 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.SdkConstants
20 import com.android.SdkConstants.ATTR_VALUE
21 import com.android.SdkConstants.INT_DEF_ANNOTATION
22 import com.android.SdkConstants.LONG_DEF_ANNOTATION
23 import com.android.SdkConstants.STRING_DEF_ANNOTATION
24 import com.android.tools.lint.annotations.Extractor.ANDROID_INT_DEF
25 import com.android.tools.lint.annotations.Extractor.ANDROID_LONG_DEF
26 import com.android.tools.lint.annotations.Extractor.ANDROID_STRING_DEF
27 import com.android.tools.metalava.ANDROIDX_ANNOTATION_PREFIX
28 import com.android.tools.metalava.ANDROIDX_NONNULL
29 import com.android.tools.metalava.ANDROIDX_NULLABLE
30 import com.android.tools.metalava.ANDROID_NONNULL
31 import com.android.tools.metalava.ANDROID_NULLABLE
32 import com.android.tools.metalava.ApiPredicate
33 import com.android.tools.metalava.JAVA_LANG_PREFIX
34 import com.android.tools.metalava.Options
35 import com.android.tools.metalava.RECENTLY_NONNULL
36 import com.android.tools.metalava.RECENTLY_NULLABLE
37 import com.android.tools.metalava.model.psi.PsiBasedCodebase
38 import com.android.tools.metalava.options
39 import com.intellij.psi.PsiCallExpression
40 import com.intellij.psi.PsiField
41 import com.intellij.psi.PsiModifierListOwner
42 import com.intellij.psi.PsiReference
43 import org.jetbrains.kotlin.psi.KtObjectDeclaration
44 import org.jetbrains.uast.UElement
45
isNullableAnnotationnull46 fun isNullableAnnotation(qualifiedName: String): Boolean {
47 return qualifiedName.endsWith("Nullable")
48 }
49
isNonNullAnnotationnull50 fun isNonNullAnnotation(qualifiedName: String): Boolean {
51 return qualifiedName.endsWith("NonNull") ||
52 qualifiedName.endsWith("NotNull") ||
53 qualifiedName.endsWith("Nonnull")
54 }
55
isJvmSyntheticAnnotationnull56 fun isJvmSyntheticAnnotation(qualifiedName: String): Boolean {
57 return qualifiedName == "kotlin.jvm.JvmSynthetic"
58 }
59
60 interface AnnotationItem {
61 val codebase: Codebase
62
63 /** Fully qualified name of the annotation */
64 val qualifiedName: String?
65
66 /** Fully qualified name of the annotation (prior to name mapping) */
67 val originalName: String?
68
69 /** Generates source code for this annotation (using fully qualified names) */
toSourcenull70 fun toSource(
71 target: AnnotationTarget = AnnotationTarget.SIGNATURE_FILE,
72 showDefaultAttrs: Boolean = true
73 ): String
74
75 /** The applicable targets for this annotation */
76 val targets: Set<AnnotationTarget>
77
78 /** Attributes of the annotation (may be empty) */
79 val attributes: List<AnnotationAttribute>
80
81 /** True if this annotation represents @Nullable or @NonNull (or some synonymous annotation) */
82 fun isNullnessAnnotation(): Boolean {
83 return isNullable() || isNonNull()
84 }
85
86 /** True if this annotation represents @Nullable (or some synonymous annotation) */
isNullablenull87 fun isNullable(): Boolean {
88 return isNullableAnnotation(qualifiedName ?: return false)
89 }
90
91 /** True if this annotation represents @NonNull (or some synonymous annotation) */
isNonNullnull92 fun isNonNull(): Boolean {
93 return isNonNullAnnotation(qualifiedName ?: return false)
94 }
95
96 /** True if this annotation represents @JvmSynthetic */
isJvmSyntheticnull97 fun isJvmSynthetic(): Boolean {
98 return isJvmSyntheticAnnotation(qualifiedName ?: return false)
99 }
100
101 /** True if this annotation represents @IntDef, @LongDef or @StringDef */
isTypeDefAnnotationnull102 fun isTypeDefAnnotation(): Boolean {
103 val name = qualifiedName ?: return false
104 if (!(name.endsWith("Def"))) {
105 return false
106 }
107 return (
108 INT_DEF_ANNOTATION.isEquals(name) ||
109 STRING_DEF_ANNOTATION.isEquals(name) ||
110 LONG_DEF_ANNOTATION.isEquals(name) ||
111 ANDROID_INT_DEF == name ||
112 ANDROID_STRING_DEF == name ||
113 ANDROID_LONG_DEF == name
114 )
115 }
116
117 /**
118 * True if this annotation represents a @ParameterName annotation (or some synonymous annotation).
119 * The parameter name should be the default attribute or "value".
120 */
isParameterNamenull121 fun isParameterName(): Boolean {
122 return qualifiedName?.endsWith(".ParameterName") ?: return false
123 }
124
125 /**
126 * True if this annotation represents a @DefaultValue annotation (or some synonymous annotation).
127 * The default value should be the default attribute or "value".
128 */
isDefaultValuenull129 fun isDefaultValue(): Boolean {
130 return qualifiedName?.endsWith(".DefaultValue") ?: return false
131 }
132
133 /** Returns the given named attribute if specified */
findAttributenull134 fun findAttribute(name: String?): AnnotationAttribute? {
135 val actualName = name ?: ATTR_VALUE
136 return attributes.firstOrNull { it.name == actualName }
137 }
138
139 /** Find the class declaration for the given annotation */
resolvenull140 fun resolve(): ClassItem? {
141 return codebase.findClass(qualifiedName ?: return null)
142 }
143
144 /** If this annotation has a typedef annotation associated with it, return it */
findTypedefAnnotationnull145 fun findTypedefAnnotation(): AnnotationItem? {
146 val className = originalName ?: return null
147 return codebase.findClass(className)?.modifiers?.annotations()?.firstOrNull { it.isTypeDefAnnotation() }
148 }
149
150 /** Returns the retention of this annotation */
151 val retention: AnnotationRetention
152 get() {
153 val name = qualifiedName
154 if (name != null) {
155 val cls = codebase.findClass(name) ?: (codebase as? PsiBasedCodebase)?.findOrCreateClass(name)
156 if (cls != null) {
157 if (cls.isAnnotationType()) {
158 return cls.getRetention()
159 }
160 }
161 }
162
163 return AnnotationRetention.CLASS
164 }
165
166 companion object {
167 /** The simple name of an annotation, which is the annotation name (not qualified name) prefixed by @ */
simpleNamenull168 fun simpleName(item: AnnotationItem): String {
169 return item.qualifiedName?.let { "@${it.substringAfterLast('.')}" }.orEmpty()
170 }
171
172 /**
173 * Maps an annotation name to the name to be used in signatures/stubs/external annotation files.
174 * Annotations that should not be exported are mapped to null.
175 */
mapNamenull176 fun mapName(
177 qualifiedName: String?,
178 target: AnnotationTarget = AnnotationTarget.SIGNATURE_FILE
179 ): String? {
180 qualifiedName ?: return null
181 if (options.passThroughAnnotations.contains(qualifiedName) ||
182 options.showAnnotations.matches(qualifiedName) ||
183 options.hideAnnotations.matches(qualifiedName)
184 ) {
185 return qualifiedName
186 }
187 if (options.excludeAnnotations.contains(qualifiedName)) {
188 return null
189 }
190
191 when (qualifiedName) {
192 // Resource annotations
193 "android.annotation.AnimRes" -> return "androidx.annotation.AnimRes"
194 "android.annotation.AnimatorRes" -> return "androidx.annotation.AnimatorRes"
195 "android.annotation.AnyRes" -> return "androidx.annotation.AnyRes"
196 "android.annotation.ArrayRes" -> return "androidx.annotation.ArrayRes"
197 "android.annotation.AttrRes" -> return "androidx.annotation.AttrRes"
198 "android.annotation.BoolRes" -> return "androidx.annotation.BoolRes"
199 "android.annotation.ColorRes" -> return "androidx.annotation.ColorRes"
200 "android.annotation.DimenRes" -> return "androidx.annotation.DimenRes"
201 "android.annotation.DrawableRes" -> return "androidx.annotation.DrawableRes"
202 "android.annotation.FontRes" -> return "androidx.annotation.FontRes"
203 "android.annotation.FractionRes" -> return "androidx.annotation.FractionRes"
204 "android.annotation.IdRes" -> return "androidx.annotation.IdRes"
205 "android.annotation.IntegerRes" -> return "androidx.annotation.IntegerRes"
206 "android.annotation.InterpolatorRes" -> return "androidx.annotation.InterpolatorRes"
207 "android.annotation.LayoutRes" -> return "androidx.annotation.LayoutRes"
208 "android.annotation.MenuRes" -> return "androidx.annotation.MenuRes"
209 "android.annotation.PluralsRes" -> return "androidx.annotation.PluralsRes"
210 "android.annotation.RawRes" -> return "androidx.annotation.RawRes"
211 "android.annotation.StringRes" -> return "androidx.annotation.StringRes"
212 "android.annotation.StyleRes" -> return "androidx.annotation.StyleRes"
213 "android.annotation.StyleableRes" -> return "androidx.annotation.StyleableRes"
214 "android.annotation.TransitionRes" -> return "androidx.annotation.TransitionRes"
215 "android.annotation.XmlRes" -> return "androidx.annotation.XmlRes"
216
217 // Threading
218 "android.annotation.AnyThread" -> return "androidx.annotation.AnyThread"
219 "android.annotation.BinderThread" -> return "androidx.annotation.BinderThread"
220 "android.annotation.MainThread" -> return "androidx.annotation.MainThread"
221 "android.annotation.UiThread" -> return "androidx.annotation.UiThread"
222 "android.annotation.WorkerThread" -> return "androidx.annotation.WorkerThread"
223
224 // Colors
225 "android.annotation.ColorInt" -> return "androidx.annotation.ColorInt"
226 "android.annotation.ColorLong" -> return "androidx.annotation.ColorLong"
227 "android.annotation.HalfFloat" -> return "androidx.annotation.HalfFloat"
228
229 // Ranges and sizes
230 "android.annotation.FloatRange" -> return "androidx.annotation.FloatRange"
231 "android.annotation.IntRange" -> return "androidx.annotation.IntRange"
232 "android.annotation.Size" -> return "androidx.annotation.Size"
233 "android.annotation.Px" -> return "androidx.annotation.Px"
234 "android.annotation.Dimension" -> return "androidx.annotation.Dimension"
235
236 // Null
237 // We only change recently/newly nullable annotation in stubs
238 RECENTLY_NULLABLE -> return if (target == AnnotationTarget.SDK_STUBS_FILE) qualifiedName else ANDROIDX_NULLABLE
239 RECENTLY_NONNULL -> return if (target == AnnotationTarget.SDK_STUBS_FILE) qualifiedName else ANDROIDX_NONNULL
240
241 ANDROIDX_NULLABLE,
242 ANDROID_NULLABLE,
243 "libcore.util.Nullable",
244 "org.jetbrains.annotations.Nullable" -> return nullableAnnotationName(target)
245
246 ANDROIDX_NONNULL,
247 ANDROID_NONNULL,
248 "libcore.util.NonNull",
249 "org.jetbrains.annotations.NotNull" -> return nonNullAnnotationName(target)
250
251 // Typedefs
252 "android.annotation.IntDef" -> return "androidx.annotation.IntDef"
253 "android.annotation.StringDef" -> return "androidx.annotation.StringDef"
254 "android.annotation.LongDef" -> return "androidx.annotation.LongDef"
255
256 // Context Types
257 "android.annotation.UiContext" -> return "androidx.annotation.UiContext"
258 "android.annotation.DisplayContext" -> return "androidx.annotation.DisplayContext"
259 "android.annotation.NonUiContext" -> return "androidx.annotation.NonUiContext"
260
261 // Misc
262 "android.annotation.DeprecatedForSdk" -> return "java.lang.Deprecated"
263 "android.annotation.CallSuper" -> return "androidx.annotation.CallSuper"
264 "android.annotation.CheckResult" -> return "androidx.annotation.CheckResult"
265 "android.annotation.Discouraged" -> return "androidx.annotation.Discouraged"
266 "android.annotation.RequiresPermission" -> return "androidx.annotation.RequiresPermission"
267 "android.annotation.RequiresPermission.Read" -> return "androidx.annotation.RequiresPermission.Read"
268 "android.annotation.RequiresPermission.Write" -> return "androidx.annotation.RequiresPermission.Write"
269
270 // These aren't support annotations, but could/should be:
271 "android.annotation.CurrentTimeMillisLong",
272 "android.annotation.DurationMillisLong",
273 "android.annotation.ElapsedRealtimeLong",
274 "android.annotation.UserIdInt",
275 "android.annotation.BytesLong",
276
277 // These aren't support annotations
278 "android.annotation.AppIdInt",
279 "android.annotation.SuppressAutoDoc",
280 "android.annotation.SystemApi",
281 "android.annotation.TestApi",
282 "android.annotation.CallbackExecutor",
283 "android.annotation.Condemned",
284 "android.annotation.Hide",
285
286 "android.annotation.Widget" -> return qualifiedName
287
288 // Included for analysis, but should not be exported:
289 "android.annotation.BroadcastBehavior",
290 "android.annotation.SdkConstant",
291 "android.annotation.RequiresFeature",
292 "android.annotation.SystemService" -> return qualifiedName
293
294 // Should not be mapped to a different package name:
295 "android.annotation.TargetApi",
296 "android.annotation.SuppressLint" -> return qualifiedName
297
298 else -> {
299 // Some new annotations added to the platform: assume they are support annotations?
300 return when {
301 // Special Kotlin annotations recognized by the compiler: map to supported package name
302 qualifiedName.endsWith(".ParameterName") || qualifiedName.endsWith(".DefaultValue") ->
303 "kotlin.annotations.jvm.internal${qualifiedName.substring(qualifiedName.lastIndexOf('.'))}"
304
305 // Other third party nullness annotations?
306 isNullableAnnotation(qualifiedName) -> nullableAnnotationName(target)
307 isNonNullAnnotation(qualifiedName) -> nonNullAnnotationName(target)
308
309 // AndroidX annotations are all included, as is the built-in stuff like @Retention
310 qualifiedName.startsWith(ANDROIDX_ANNOTATION_PREFIX) -> return qualifiedName
311 qualifiedName.startsWith(JAVA_LANG_PREFIX) -> return qualifiedName
312
313 // Unknown Android platform annotations
314 qualifiedName.startsWith("android.annotation.") -> {
315 return null
316 }
317
318 else -> qualifiedName
319 }
320 }
321 }
322 }
323
nullableAnnotationNamenull324 private fun nullableAnnotationName(target: AnnotationTarget) =
325 if (target == AnnotationTarget.SDK_STUBS_FILE) ANDROID_NULLABLE else ANDROIDX_NULLABLE
326
327 private fun nonNullAnnotationName(target: AnnotationTarget) =
328 if (target == AnnotationTarget.SDK_STUBS_FILE) ANDROID_NONNULL else ANDROIDX_NONNULL
329
330 private val TYPEDEF_ANNOTATION_TARGETS =
331 if (options.typedefMode == Options.TypedefMode.INLINE ||
332 options.typedefMode == Options.TypedefMode.NONE
333 ) // just here for compatibility purposes
334 ANNOTATION_EXTERNAL
335 else
336 ANNOTATION_EXTERNAL_ONLY
337
338 /** The applicable targets for this annotation */
339 fun computeTargets(
340 annotation: AnnotationItem,
341 classFinder: (String) -> ClassItem?
342 ): Set<AnnotationTarget> {
343 val qualifiedName = annotation.qualifiedName ?: return NO_ANNOTATION_TARGETS
344 if (options.passThroughAnnotations.contains(qualifiedName)) {
345 return ANNOTATION_IN_ALL_STUBS
346 }
347 when (qualifiedName) {
348
349 // The typedef annotations are special: they should not be in the signature
350 // files, but we want to include them in the external annotations file such that tools
351 // can enforce them.
352 "android.annotation.IntDef",
353 "androidx.annotation.IntDef",
354 "android.annotation.StringDef",
355 "androidx.annotation.StringDef",
356 "android.annotation.LongDef",
357 "androidx.annotation.LongDef" -> return TYPEDEF_ANNOTATION_TARGETS
358
359 // Not directly API relevant
360 "android.view.ViewDebug.ExportedProperty",
361 "android.view.ViewDebug.CapturedViewProperty" -> return ANNOTATION_STUBS_ONLY
362
363 // Retained in the sdk/jar stub source code so that SdkConstant files can be extracted
364 // from those. This is useful for modularizing the main SDK stubs without having to
365 // add a separate module SDK artifact for sdk constants.
366 "android.annotation.SdkConstant" -> return ANNOTATION_SDK_STUBS_ONLY
367
368 // Skip known annotations that we (a) never want in external annotations and (b) we are
369 // specially overwriting anyway in the stubs (and which are (c) not API significant)
370 "com.android.modules.annotation.MinSdk",
371 "java.lang.annotation.Native",
372 "java.lang.SuppressWarnings",
373 "java.lang.Override",
374 "kotlin.Suppress",
375 "androidx.annotation.experimental.UseExperimental",
376 "androidx.annotation.OptIn",
377 "kotlin.UseExperimental",
378 "kotlin.OptIn" -> return NO_ANNOTATION_TARGETS
379
380 // These optimization-related annotations shouldn't be exported.
381 "dalvik.annotation.optimization.CriticalNative",
382 "dalvik.annotation.optimization.FastNative",
383 "dalvik.annotation.optimization.NeverCompile",
384 "dalvik.annotation.optimization.NeverInline",
385 "dalvik.annotation.optimization.ReachabilitySensitive" ->
386 return NO_ANNOTATION_TARGETS
387
388 // TODO(aurimas): consider using annotation directly instead of modifiers
389 "kotlin.Deprecated" -> return NO_ANNOTATION_TARGETS // tracked separately as a pseudo-modifier
390 "android.annotation.DeprecatedForSdk",
391 "java.lang.Deprecated", // tracked separately as a pseudo-modifier
392
393 // Below this when-statement we perform the correct lookup: check API predicate, and check
394 // that retention is class or runtime, but we've hardcoded the answers here
395 // for some common annotations.
396
397 "android.widget.RemoteViews.RemoteView",
398
399 "kotlin.annotation.Target",
400 "kotlin.annotation.Retention",
401 "kotlin.annotation.Repeatable",
402 "kotlin.annotation.MustBeDocumented",
403 "kotlin.DslMarker",
404 "kotlin.PublishedApi",
405 "kotlin.ExtensionFunctionType",
406
407 "java.lang.FunctionalInterface",
408 "java.lang.SafeVarargs",
409 "java.lang.annotation.Documented",
410 "java.lang.annotation.Inherited",
411 "java.lang.annotation.Repeatable",
412 "java.lang.annotation.Retention",
413 "java.lang.annotation.Target" -> return ANNOTATION_IN_ALL_STUBS
414
415 // Metalava already tracks all the methods that get generated due to these annotations.
416 "kotlin.jvm.JvmOverloads",
417 "kotlin.jvm.JvmField",
418 "kotlin.jvm.JvmStatic",
419 "kotlin.jvm.JvmName" -> return NO_ANNOTATION_TARGETS
420 }
421
422 // @android.annotation.Nullable and NonNullable specially recognized annotations by the Kotlin
423 // compiler 1.3 and above: they always go in the stubs.
424 if (qualifiedName == ANDROID_NULLABLE ||
425 qualifiedName == ANDROID_NONNULL ||
426 qualifiedName == ANDROIDX_NULLABLE ||
427 qualifiedName == ANDROIDX_NONNULL
428 ) {
429 return ANNOTATION_IN_ALL_STUBS
430 }
431
432 if (qualifiedName.startsWith("android.annotation.")) {
433 // internal annotations not mapped to androidx: things like @SystemApi. Skip from
434 // stubs, external annotations, signature files, etc.
435 return NO_ANNOTATION_TARGETS
436 }
437
438 // @RecentlyNullable and @RecentlyNonNull are specially recognized annotations by the Kotlin
439 // compiler: they always go in the stubs.
440 if (qualifiedName == RECENTLY_NULLABLE ||
441 qualifiedName == RECENTLY_NONNULL
442 ) {
443 return ANNOTATION_IN_ALL_STUBS
444 }
445
446 // Determine the retention of the annotation: source retention annotations go
447 // in the external annotations file, class and runtime annotations go in
448 // the stubs files (except for the androidx annotations which are not included
449 // in the SDK and therefore cannot be referenced from it due to apt's unfortunate
450 // habit of loading all annotation classes it encounters.)
451
452 if (qualifiedName.startsWith("androidx.annotation.")) {
453 if (qualifiedName == ANDROIDX_NULLABLE || qualifiedName == ANDROIDX_NONNULL) {
454 // Right now, nullness annotations (other than @RecentlyNullable and @RecentlyNonNull)
455 // have to go in external annotations since they aren't in the class path for
456 // annotation processors. However, we do want them showing up in the documentation using
457 // their real annotation names.
458 return ANNOTATION_IN_DOC_STUBS_AND_EXTERNAL
459 }
460
461 return ANNOTATION_EXTERNAL
462 }
463
464 // See if the annotation is pointing to an annotation class that is part of the API; if not, skip it.
465 val cls = classFinder(qualifiedName) ?: return NO_ANNOTATION_TARGETS
466 if (!ApiPredicate().test(cls)) {
467 if (options.typedefMode != Options.TypedefMode.NONE) {
468 if (cls.modifiers.annotations().any { it.isTypeDefAnnotation() }) {
469 return ANNOTATION_SIGNATURE_ONLY
470 }
471 }
472
473 return NO_ANNOTATION_TARGETS
474 }
475
476 if (cls.isAnnotationType()) {
477 val retention = cls.getRetention()
478 if (retention == AnnotationRetention.RUNTIME || retention == AnnotationRetention.CLASS) {
479 return ANNOTATION_IN_ALL_STUBS
480 }
481 }
482
483 return ANNOTATION_EXTERNAL
484 }
485
486 /**
487 * Given a "full" annotation name, shortens it by removing redundant package names.
488 * This is intended to be used to reduce clutter in signature files.
489 *
490 * For example, this method will convert `@androidx.annotation.Nullable` to just
491 * `@Nullable`, and `@androidx.annotation.IntRange(from=20)` to `IntRange(from=20)`.
492 */
shortenAnnotationnull493 fun shortenAnnotation(source: String): String {
494 return when {
495 source == "@java.lang.Deprecated" -> "@Deprecated"
496 source.startsWith("android.annotation.", 1) -> {
497 "@" + source.substring("@android.annotation.".length)
498 }
499 source.startsWith(ANDROIDX_ANNOTATION_PREFIX, 1) -> {
500 "@" + source.substring("@androidx.annotation.".length)
501 }
502 else -> source
503 }
504 }
505
506 /**
507 * Reverses the [shortenAnnotation] method. Intended for use when reading in signature files
508 * that contain shortened type references.
509 */
unshortenAnnotationnull510 fun unshortenAnnotation(source: String): String {
511 return when {
512 source == "@Deprecated" -> "@java.lang.Deprecated"
513 // The first 3 annotations are in the android.annotation. package, not androidx.annotation
514 // Nullability annotations are written as @NonNull and @Nullable in API text files,
515 // and these should be linked no android.annotation package when generating stubs.
516 source.startsWith("@SystemService") ||
517 source.startsWith("@TargetApi") ||
518 source.startsWith("@SuppressLint") ||
519 source.startsWith("@Nullable") ||
520 source.startsWith("@NonNull") ->
521 "@android.annotation." + source.substring(1)
522 // If the first character of the name (after "@") is lower-case, then
523 // assume it's a package name, so no need to shorten it.
524 source.startsWith("@") && source[1].isLowerCase() -> source
525 else -> {
526 "@androidx.annotation." + source.substring(1)
527 }
528 }
529 }
530
531 /**
532 * If the given element has an *implicit* nullness, return it. This returns
533 * true for implicitly nullable elements, such as the parameter to the equals
534 * method, false for implicitly non null elements (such as annotation type
535 * members), and null if there is no implicit nullness.
536 */
getImplicitNullnessnull537 fun getImplicitNullness(item: Item): Boolean? {
538 var nullable: Boolean? = null
539
540 // Is this a Kotlin object declaration (such as a companion object) ?
541 // If so, it is always non null.
542 val sourcePsi = item.psi()
543 if (sourcePsi is UElement && sourcePsi.sourcePsi is KtObjectDeclaration) {
544 nullable = false
545 }
546
547 // Constant field not initialized to null?
548 if (item is FieldItem &&
549 (item.isEnumConstant() || item.modifiers.isFinal() && item.initialValue(false) != null)
550 ) {
551 // Assigned to constant: not nullable
552 nullable = false
553 } else if (item is FieldItem && item.modifiers.isFinal()) {
554 // If we're looking at a final field, look at the right hand side
555 // of the field to the field initialization. If that right hand side
556 // for example represents a method call, and the method we're calling
557 // is annotated with @NonNull, then the field (since it is final) will
558 // always be @NonNull as well.
559 val initializer = (item.psi() as? PsiField)?.initializer
560 if (initializer != null && initializer is PsiReference) {
561 val resolved = initializer.resolve()
562 if (resolved is PsiModifierListOwner &&
563 resolved.annotations.any {
564 isNonNullAnnotation(it.qualifiedName ?: "")
565 }
566 ) {
567 nullable = false
568 }
569 } else if (initializer != null && initializer is PsiCallExpression) {
570 val resolved = initializer.resolveMethod()
571 if (resolved != null &&
572 resolved.annotations.any {
573 isNonNullAnnotation(it.qualifiedName ?: "")
574 }
575 ) {
576 nullable = false
577 }
578 }
579 } else if (item.synthetic && (
580 item is MethodItem && item.isEnumSyntheticMethod() ||
581 item is ParameterItem && item.containingMethod().isEnumSyntheticMethod()
582 )
583 ) {
584 // Workaround the fact that the Kotlin synthetic enum methods
585 // do not have nullness information
586 nullable = false
587 }
588
589 // Annotation type members cannot be null
590 if (item is MemberItem && item.containingClass().isAnnotationType()) {
591 nullable = false
592 }
593
594 // Equals and toString have known nullness
595 if (item is MethodItem && item.name() == "toString" && item.parameters().isEmpty()) {
596 nullable = false
597 } else if (item is ParameterItem && item.containingMethod().name() == "equals" &&
598 item.containingMethod().parameters().size == 1
599 ) {
600 nullable = true
601 }
602
603 return nullable
604 }
605 }
606 }
607
608 /** Default implementation of an annotation item */
609 abstract class DefaultAnnotationItem(override val codebase: Codebase) : AnnotationItem {
<lambda>null610 override val targets: Set<AnnotationTarget> by lazy {
611 AnnotationItem.computeTargets(this, codebase::findClass)
612 }
613 }
614
615 /** An attribute of an annotation, such as "value" */
616 interface AnnotationAttribute {
617 /** The name of the annotation */
618 val name: String
619 /** The annotation value */
620 val value: AnnotationAttributeValue
621
622 /**
623 * Return all leaf values; this flattens the complication of handling
624 * {@code @SuppressLint("warning")} and {@code @SuppressLint({"warning1","warning2"})
625 */
leafValuesnull626 fun leafValues(): List<AnnotationAttributeValue> {
627 val result = mutableListOf<AnnotationAttributeValue>()
628 AnnotationAttributeValue.addValues(value, result)
629 return result
630 }
631 }
632
633 /** An annotation value */
634 interface AnnotationAttributeValue {
635 /** Generates source code for this annotation value */
toSourcenull636 fun toSource(): String
637
638 /** The value of the annotation */
639 fun value(): Any?
640
641 /** If the annotation declaration references a field (or class etc), return the resolved class */
642 fun resolve(): Item?
643
644 companion object {
645 fun addValues(value: AnnotationAttributeValue, into: MutableList<AnnotationAttributeValue>) {
646 if (value is AnnotationArrayAttributeValue) {
647 for (v in value.values) {
648 addValues(v, into)
649 }
650 } else if (value is AnnotationSingleAttributeValue) {
651 into.add(value)
652 }
653 }
654 }
655 }
656
657 /** An annotation value (for a single item, not an array) */
658 interface AnnotationSingleAttributeValue : AnnotationAttributeValue {
659 /** The annotation value, expressed as source code */
660 val valueSource: String
661 /** The annotation value */
662 val value: Any?
663
valuenull664 override fun value() = value
665 }
666
667 /** An annotation value for an array of items */
668 interface AnnotationArrayAttributeValue : AnnotationAttributeValue {
669 /** The annotation values */
670 val values: List<AnnotationAttributeValue>
671
672 override fun resolve(): Item? {
673 error("resolve() should not be called on an array value")
674 }
675
676 override fun value() = values.mapNotNull { it.value() }.toTypedArray()
677 }
678
679 class DefaultAnnotationAttribute(
680 override val name: String,
681 override val value: DefaultAnnotationValue
682 ) : AnnotationAttribute {
683 companion object {
createnull684 fun create(name: String, value: String): DefaultAnnotationAttribute {
685 return DefaultAnnotationAttribute(name, DefaultAnnotationValue.create(value))
686 }
687
createListnull688 fun createList(source: String): List<AnnotationAttribute> {
689 val list = mutableListOf<AnnotationAttribute>() // TODO: default size = 2
690 var begin = 0
691 var index = 0
692 val length = source.length
693 while (index < length) {
694 val c = source[index]
695 if (c == '{') {
696 index = findEnd(source, index + 1, length, '}')
697 } else if (c == '"') {
698 index = findEnd(source, index + 1, length, '"')
699 } else if (c == ',') {
700 addAttribute(list, source, begin, index)
701 index++
702 begin = index
703 continue
704 } else if (c == ' ' && index == begin) {
705 begin++
706 }
707
708 index++
709 }
710
711 if (begin < length) {
712 addAttribute(list, source, begin, length)
713 }
714
715 return list
716 }
717
findEndnull718 private fun findEnd(source: String, from: Int, to: Int, sentinel: Char): Int {
719 var i = from
720 while (i < to) {
721 val c = source[i]
722 if (c == '\\') {
723 i++
724 } else if (c == sentinel) {
725 return i
726 }
727 i++
728 }
729 return to
730 }
731
addAttributenull732 private fun addAttribute(list: MutableList<AnnotationAttribute>, source: String, from: Int, to: Int) {
733 var split = source.indexOf('=', from)
734 if (split >= to) {
735 split = -1
736 }
737 val name: String
738 val value: String
739 val valueBegin: Int
740 val valueEnd: Int
741 if (split == -1) {
742 valueBegin = split + 1
743 valueEnd = to
744 name = "value"
745 } else {
746 name = source.substring(from, split).trim()
747 valueBegin = split + 1
748 valueEnd = to
749 }
750 value = source.substring(valueBegin, valueEnd).trim()
751 list.add(DefaultAnnotationAttribute.create(name, value))
752 }
753 }
754
toStringnull755 override fun toString(): String {
756 return "DefaultAnnotationAttribute(name='$name', value=$value)"
757 }
758 }
759
760 abstract class DefaultAnnotationValue : AnnotationAttributeValue {
761 companion object {
createnull762 fun create(value: String): DefaultAnnotationValue {
763 return if (value.startsWith("{")) { // Array
764 DefaultAnnotationArrayAttributeValue(value)
765 } else {
766 DefaultAnnotationSingleAttributeValue(value)
767 }
768 }
769 }
770
toStringnull771 override fun toString(): String = toSource()
772 }
773
774 class DefaultAnnotationSingleAttributeValue(override val valueSource: String) :
775 DefaultAnnotationValue(),
776 AnnotationSingleAttributeValue {
777 @Suppress("IMPLICIT_CAST_TO_ANY")
778 override val value = when {
779 valueSource == SdkConstants.VALUE_TRUE -> true
780 valueSource == SdkConstants.VALUE_FALSE -> false
781 valueSource.startsWith("\"") -> valueSource.removeSurrounding("\"")
782 valueSource.startsWith('\'') -> valueSource.removeSurrounding("'")[0]
783 else -> try {
784 if (valueSource.contains(".")) {
785 valueSource.toDouble()
786 } else {
787 valueSource.toLong()
788 }
789 } catch (e: NumberFormatException) {
790 valueSource
791 }
792 }
793
794 override fun resolve(): Item? = null
795
796 override fun toSource() = valueSource
797 }
798
799 class DefaultAnnotationArrayAttributeValue(val value: String) :
800 DefaultAnnotationValue(),
801 AnnotationArrayAttributeValue {
802 init {
<lambda>null803 assert(value.startsWith("{") && value.endsWith("}")) { value }
804 }
805
<lambda>null806 override val values = value.substring(1, value.length - 1).split(",").map {
807 DefaultAnnotationValue.create(it.trim())
808 }.toList()
809
toSourcenull810 override fun toSource() = value
811 }
812