• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.DocLevel
20 import com.android.tools.metalava.DocLevel.HIDDEN
21 import com.android.tools.metalava.DocLevel.PACKAGE
22 import com.android.tools.metalava.DocLevel.PRIVATE
23 import com.android.tools.metalava.DocLevel.PROTECTED
24 import com.android.tools.metalava.DocLevel.PUBLIC
25 import com.android.tools.metalava.Options
26 import com.android.tools.metalava.options
27 import java.io.Writer
28 
29 interface ModifierList {
30     val codebase: Codebase
31     fun annotations(): List<AnnotationItem>
32 
33     fun owner(): Item
34     fun getVisibilityLevel(): VisibilityLevel
35     fun isPublic(): Boolean
36     fun isProtected(): Boolean
37     fun isPrivate(): Boolean
38     fun isStatic(): Boolean
39     fun isAbstract(): Boolean
40     fun isFinal(): Boolean
41     fun isNative(): Boolean
42     fun isSynchronized(): Boolean
43     fun isStrictFp(): Boolean
44     fun isTransient(): Boolean
45     fun isVolatile(): Boolean
46     fun isDefault(): Boolean
47 
48     // Modifier in Kotlin, separate syntax (...) in Java but modeled as modifier here
49     fun isVarArg(): Boolean = false
50 
51     // Kotlin
52     fun isSealed(): Boolean = false
53     fun isFunctional(): Boolean = false
54     fun isCompanion(): Boolean = false
55     fun isInfix(): Boolean = false
56     fun isConst(): Boolean = false
57     fun isSuspend(): Boolean = false
58     fun isOperator(): Boolean = false
59     fun isInline(): Boolean = false
60     fun isValue(): Boolean = false
61     fun isData(): Boolean = false
62     fun isEmpty(): Boolean
63 
64     fun isPackagePrivate() = !(isPublic() || isProtected() || isPrivate())
65     fun isPublicOrProtected() = isPublic() || isProtected()
66 
67     // Rename? It's not a full equality, it's whether an override's modifier set is significant
68     fun equivalentTo(other: ModifierList): Boolean {
69         if (isPublic() != other.isPublic()) return false
70         if (isProtected() != other.isProtected()) return false
71         if (isPrivate() != other.isPrivate()) return false
72 
73         if (isStatic() != other.isStatic()) return false
74         if (isAbstract() != other.isAbstract()) return false
75         if (isFinal() != other.isFinal()) { return false }
76         if (isTransient() != other.isTransient()) return false
77         if (isVolatile() != other.isVolatile()) return false
78 
79         // Default does not require an override to "remove" it
80         // if (isDefault() != other.isDefault()) return false
81 
82         return true
83     }
84 
85     /** Returns true if this modifier list contains any nullness information */
86     fun hasNullnessInfo(): Boolean {
87         return annotations().any { it.isNonNull() || it.isNullable() }
88     }
89 
90     /** Returns true if this modifier list contains any a Nullable annotation */
91     fun isNullable(): Boolean {
92         return annotations().any { it.isNullable() }
93     }
94 
95     /** Returns true if this modifier list contains any a NonNull annotation */
96     fun isNonNull(): Boolean {
97         return annotations().any { it.isNonNull() }
98     }
99 
100     /**
101      * Returns true if this modifier list contains the `@JvmSynthetic` annotation
102      */
103     fun hasJvmSyntheticAnnotation(): Boolean {
104         return annotations().any { it.isJvmSynthetic() }
105     }
106 
107     /**
108      * Returns true if this modifier list contains any annotations explicitly passed in
109      * via [Options.showAnnotations]
110      */
111     fun hasShowAnnotation(): Boolean {
112         if (options.showAnnotations.isEmpty()) {
113             return false
114         }
115         return annotations().any {
116             options.showAnnotations.matches(it)
117         }
118     }
119 
120     /**
121      * Returns true if this modifier list contains any annotations explicitly passed in
122      * via [Options.showSingleAnnotations]
123      */
124     fun hasShowSingleAnnotation(): Boolean {
125 
126         if (options.showSingleAnnotations.isEmpty()) {
127             return false
128         }
129         return annotations().any {
130             options.showSingleAnnotations.matches(it)
131         }
132     }
133 
134     /**
135      * Returns true if this modifier list contains any annotations explicitly passed in
136      * via [Options.showForStubPurposesAnnotations], and this is the only showAnnotation.
137      */
138     fun onlyShowForStubPurposes(): Boolean {
139         if (options.showForStubPurposesAnnotations.isEmpty()) {
140             return false
141         }
142         return annotations().any {
143             options.showForStubPurposesAnnotations.matches(it)
144         } && !annotations().any {
145             options.showAnnotations.matches(it) && !options.showForStubPurposesAnnotations.matches(it)
146         }
147     }
148 
149     /**
150      * Returns true if this modifier list contains any annotations explicitly passed in
151      * via [Options.hideAnnotations] or any annotations which are themselves annotated
152      * with meta-annotations explicitly passed in via [Options.hideMetaAnnotations]
153      *
154      * @see hasHideMetaAnnotations
155      */
156     fun hasHideAnnotations(): Boolean {
157         if (options.hideAnnotations.isEmpty() && options.hideMetaAnnotations.isEmpty()) {
158             return false
159         }
160         return annotations().any { annotation ->
161             options.hideAnnotations.matches(annotation) ||
162                 annotation.resolve()?.hasHideMetaAnnotation() ?: false
163         }
164     }
165 
166     /**
167      * Returns true if this modifier list contains any meta-annotations explicitly passed in
168      * via [Options.hideMetaAnnotations].
169      *
170      * Hidden meta-annotations allow Metalava to handle concepts like Kotlin's [Experimental],
171      * which allows developers to create annotations that describe experimental features -- sets
172      * of distinct and potentially overlapping unstable API surfaces. Libraries may wish to exclude
173      * such sets of APIs from tracking and stub JAR generation by passing [Experimental] as a
174      * hidden meta-annotation.
175      */
176     fun hasHideMetaAnnotations(): Boolean {
177         if (options.hideMetaAnnotations.isEmpty()) {
178             return false
179         }
180         return annotations().any { annotation ->
181             options.hideMetaAnnotations.contains(annotation.qualifiedName)
182         }
183     }
184 
185     /** Returns true if this modifier list contains the given annotation */
186     fun isAnnotatedWith(qualifiedName: String): Boolean {
187         return findAnnotation(qualifiedName) != null
188     }
189 
190     /**
191      * Returns the annotation of the given qualified name (or equivalent) if found
192      * in this modifier list
193      */
194     fun findAnnotation(qualifiedName: String): AnnotationItem? {
195         val mappedName = AnnotationItem.mapName(codebase, qualifiedName)
196         return annotations().firstOrNull {
197             mappedName == it.qualifiedName
198         }
199     }
200 
201     /**
202      * Returns the annotation of the given qualified name if found in this modifier list.
203      * Like [findAnnotation], but where that method translates both the annotations in
204      * the source and the target name to their canonical form (E.g. the androidx name),
205      * this method will look at the original source for the exact name passed in here.
206      */
207     fun findExactAnnotation(qualifiedName: String): AnnotationItem? {
208         return annotations().firstOrNull {
209             qualifiedName == it.originalName
210         }
211     }
212 
213     /** Returns true if this modifier list has adequate access */
214     fun checkLevel() = checkLevel(options.docLevel)
215 
216     /**
217      * Returns true if this modifier list has access modifiers that
218      * are adequate for the given documentation level
219      */
220     fun checkLevel(level: DocLevel): Boolean {
221         if (level == HIDDEN) {
222             return true
223         } else if (owner().isHiddenOrRemoved()) {
224             return false
225         }
226         return when (level) {
227             PUBLIC -> isPublic()
228             PROTECTED -> isPublic() || isProtected()
229             PACKAGE -> !isPrivate()
230             PRIVATE, HIDDEN -> true
231         }
232     }
233 
234     /**
235      * Returns true if the visibility modifiers in this modifier list is as least as visible
236      * as the ones in the given [other] modifier list
237      */
238     fun asAccessibleAs(other: ModifierList): Boolean {
239         val otherLevel = other.getVisibilityLevel()
240         val thisLevel = getVisibilityLevel()
241         // Generally the access level enum order determines relative visibility. However, there is an exception because
242         // package private and internal are not directly comparable.
243         val result = thisLevel >= otherLevel
244         return when (otherLevel) {
245             VisibilityLevel.PACKAGE_PRIVATE -> result && thisLevel != VisibilityLevel.INTERNAL
246             VisibilityLevel.INTERNAL -> result && thisLevel != VisibilityLevel.PACKAGE_PRIVATE
247             else -> result
248         }
249     }
250 
251     /** User visible description of the visibility in this modifier list */
252     fun getVisibilityString(): String {
253         return getVisibilityLevel().userVisibleDescription
254     }
255 
256     /**
257      * Like [getVisibilityString], but package private has no modifiers; this typically corresponds to
258      * the source code for the visibility modifiers in the modifier list
259      */
260     fun getVisibilityModifiers(): String {
261         return getVisibilityLevel().javaSourceCodeModifier
262     }
263 
264     companion object {
265         fun write(
266             writer: Writer,
267             modifiers: ModifierList,
268             item: Item,
269             target: AnnotationTarget,
270             // TODO: "deprecated" isn't a modifier; clarify method name
271             includeDeprecated: Boolean = false,
272             runtimeAnnotationsOnly: Boolean = false,
273             skipNullnessAnnotations: Boolean = false,
274             omitCommonPackages: Boolean = false,
275             removeAbstract: Boolean = false,
276             removeFinal: Boolean = false,
277             addPublic: Boolean = false,
278             separateLines: Boolean = false,
279             language: Language = Language.JAVA
280         ) {
281 
282             val list = if (removeAbstract || removeFinal || addPublic) {
283                 class AbstractFiltering : ModifierList by modifiers {
284                     override fun isAbstract(): Boolean {
285                         return if (removeAbstract) false else modifiers.isAbstract()
286                     }
287 
288                     override fun isFinal(): Boolean {
289                         return if (removeFinal) false else modifiers.isFinal()
290                     }
291 
292                     override fun getVisibilityLevel(): VisibilityLevel {
293                         return if (addPublic) VisibilityLevel.PUBLIC else modifiers.getVisibilityLevel()
294                     }
295                 }
296                 AbstractFiltering()
297             } else {
298                 modifiers
299             }
300 
301             writeAnnotations(
302                 item,
303                 target,
304                 runtimeAnnotationsOnly,
305                 includeDeprecated,
306                 writer,
307                 separateLines,
308                 list,
309                 skipNullnessAnnotations,
310                 omitCommonPackages
311             )
312 
313             if (item is PackageItem) {
314                 // Packages use a modifier list, but only annotations apply
315                 return
316             }
317 
318             // Kotlin order:
319             //   https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers
320 
321             // Abstract: should appear in interfaces if in compat mode
322             val classItem = item as? ClassItem
323             val methodItem = item as? MethodItem
324 
325             val visibilityLevel = list.getVisibilityLevel()
326             val modifier = if (language == Language.JAVA) {
327                 visibilityLevel.javaSourceCodeModifier
328             } else {
329                 visibilityLevel.kotlinSourceCodeModifier
330             }
331             if (modifier.isNotEmpty()) {
332                 writer.write("$modifier ")
333             }
334 
335             val isInterface = classItem?.isInterface() == true ||
336                 (
337                     methodItem?.containingClass()?.isInterface() == true &&
338                         !list.isDefault() && !list.isStatic()
339                     )
340 
341             if (list.isAbstract() &&
342                 classItem?.isEnum() != true &&
343                 classItem?.isAnnotationType() != true &&
344                 !isInterface
345             ) {
346                 writer.write("abstract ")
347             }
348 
349             if (list.isDefault() && item !is ParameterItem) {
350                 writer.write("default ")
351             }
352 
353             if (list.isStatic() && (classItem == null || !classItem.isEnum())) {
354                 writer.write("static ")
355             }
356 
357             if (list.isFinal() &&
358                 language == Language.JAVA &&
359                 // Don't show final on parameters: that's an implementation side detail
360                 item !is ParameterItem &&
361                 classItem?.isEnum() != true
362             ) {
363                 writer.write("final ")
364             } else if (!list.isFinal() && language == Language.KOTLIN) {
365                 writer.write("open ")
366             }
367 
368             if (list.isSealed()) {
369                 writer.write("sealed ")
370             }
371 
372             if (list.isSuspend()) {
373                 writer.write("suspend ")
374             }
375 
376             if (list.isInline()) {
377                 writer.write("inline ")
378             }
379 
380             if (list.isValue()) {
381                 writer.write("value ")
382             }
383 
384             if (list.isInfix()) {
385                 writer.write("infix ")
386             }
387 
388             if (list.isOperator()) {
389                 writer.write("operator ")
390             }
391 
392             if (list.isTransient()) {
393                 writer.write("transient ")
394             }
395 
396             if (list.isVolatile()) {
397                 writer.write("volatile ")
398             }
399 
400             if (list.isSynchronized() && target.isStubsFile()) {
401                 writer.write("synchronized ")
402             }
403 
404             if (list.isNative() && target.isStubsFile()) {
405                 writer.write("native ")
406             }
407 
408             if (list.isFunctional()) {
409                 writer.write("fun ")
410             }
411 
412             if (language == Language.KOTLIN) {
413                 if (list.isData()) {
414                     writer.write("data ")
415                 }
416             }
417         }
418 
419         fun writeAnnotations(
420             item: Item,
421             target: AnnotationTarget,
422             runtimeAnnotationsOnly: Boolean,
423             includeDeprecated: Boolean,
424             writer: Writer,
425             separateLines: Boolean,
426             list: ModifierList,
427             skipNullnessAnnotations: Boolean,
428             omitCommonPackages: Boolean
429         ) {
430             //  if includeDeprecated we want to do it
431             //  unless runtimeOnly is false, in which case we'd include it too
432             // e.g. emit @Deprecated if includeDeprecated && !runtimeOnly
433             if (item.deprecated && (runtimeAnnotationsOnly || includeDeprecated)) {
434                 writer.write("@Deprecated")
435                 writer.write(if (separateLines) "\n" else " ")
436             }
437 
438             writeAnnotations(
439                 list = list,
440                 runtimeAnnotationsOnly = runtimeAnnotationsOnly,
441                 skipNullnessAnnotations = skipNullnessAnnotations,
442                 omitCommonPackages = omitCommonPackages,
443                 separateLines = separateLines,
444                 writer = writer,
445                 target = target
446             )
447         }
448 
449         fun writeAnnotations(
450             list: ModifierList,
451             skipNullnessAnnotations: Boolean = false,
452             runtimeAnnotationsOnly: Boolean = false,
453             omitCommonPackages: Boolean = false,
454             separateLines: Boolean = false,
455             filterDuplicates: Boolean = false,
456             writer: Writer,
457             target: AnnotationTarget
458         ) {
459             var annotations = list.annotations()
460 
461             // Ensure stable signature file order
462             if (annotations.size > 1) {
463                 annotations = annotations.sortedBy { it.qualifiedName }
464             }
465 
466             if (annotations.isNotEmpty()) {
467                 var index = -1
468                 for (annotation in annotations) {
469                     index++
470 
471                     if (runtimeAnnotationsOnly && annotation.retention != AnnotationRetention.RUNTIME) {
472                         continue
473                     }
474 
475                     var printAnnotation = annotation
476                     if (!annotation.targets.contains(target)) {
477                         continue
478                     } else if ((annotation.isNullnessAnnotation())) {
479                         if (skipNullnessAnnotations) {
480                             continue
481                         }
482                     } else if (annotation.qualifiedName == "java.lang.Deprecated") {
483                         // Special cased in stubs and signature files: emitted first
484                         continue
485                     } else if (options.typedefMode == Options.TypedefMode.INLINE) {
486                         val typedef = annotation.findTypedefAnnotation()
487                         if (typedef != null) {
488                             printAnnotation = typedef
489                         }
490                     } else if (options.typedefMode == Options.TypedefMode.REFERENCE &&
491                         annotation.targets === ANNOTATION_SIGNATURE_ONLY &&
492                         annotation.findTypedefAnnotation() != null
493                     ) {
494                         // For annotation references, only include the simple name
495                         writer.write("@")
496                         writer.write(annotation.resolve()?.simpleName() ?: annotation.qualifiedName!!)
497                         if (separateLines) {
498                             writer.write("\n")
499                         } else {
500                             writer.write(" ")
501                         }
502                         continue
503                     }
504 
505                     // Optionally filter out duplicates
506                     if (index > 0 && filterDuplicates) {
507                         val qualifiedName = annotation.qualifiedName
508                         var found = false
509                         for (i in 0 until index) {
510                             val prev = annotations[i]
511                             if (prev.qualifiedName == qualifiedName) {
512                                 found = true
513                                 break
514                             }
515                         }
516                         if (found) {
517                             continue
518                         }
519                     }
520 
521                     val source = printAnnotation.toSource(target, showDefaultAttrs = false)
522 
523                     if (omitCommonPackages) {
524                         writer.write(AnnotationItem.shortenAnnotation(source))
525                     } else {
526                         writer.write(source)
527                     }
528                     if (separateLines) {
529                         writer.write("\n")
530                     } else {
531                         writer.write(" ")
532                     }
533                 }
534             }
535         }
536     }
537 }
538