1 /*
2  * Copyright (C) 2023 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.turbine
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.Import
21 import com.android.tools.metalava.model.Item
22 import com.android.tools.metalava.model.SourceFile
23 import com.google.turbine.diag.LineMap
24 import com.google.turbine.tree.Tree.CompUnit
25 import java.util.TreeSet
26 import java.util.function.Predicate
27 
28 internal class TurbineSourceFile(
29     val codebase: TurbineBasedCodebase,
30     val compUnit: CompUnit,
31 ) : SourceFile {
32 
getHeaderCommentsnull33     override fun getHeaderComments() = getHeaderComments(compUnit.source().source())
34 
35     override fun classes(): Sequence<ClassItem> {
36         val pkgName = getPackageName(compUnit)
37         val classDecls = compUnit.decls() // Top level class declarations
38         val classNames = classDecls.map { pkgName + "." + it.name().value() }
39         return classNames.asSequence().mapNotNull { codebase.findClass(it) }
40     }
41 
equalsnull42     override fun equals(other: Any?): Boolean {
43         if (this === other) return true
44         return other is TurbineSourceFile && compUnit == other.compUnit
45     }
46 
hashCodenull47     override fun hashCode(): Int {
48         return compUnit.hashCode()
49     }
50 
getImportsnull51     override fun getImports(predicate: Predicate<Item>): Collection<Import> {
52         val imports = TreeSet<Import>(compareBy { it.pattern })
53 
54         for (import in compUnit.imports()) {
55             val resolvedName = extractNameFromIdent(import.type())
56             // Package import
57             if (import.wild()) {
58                 val pkgItem = codebase.findPackage(resolvedName) ?: continue
59                 if (
60                     predicate.test(pkgItem) &&
61                         // Also make sure it isn't an empty package (after applying the
62                         // filter)
63                         // since in that case we'd have an invalid import
64                         pkgItem.topLevelClasses().any { it.emit && predicate.test(it) }
65                 ) {
66                     imports.add(Import(pkgItem))
67                 }
68             }
69             // Not static member import i.e. class import
70             else if (!import.stat()) {
71                 val classItem = codebase.findClass(resolvedName) ?: continue
72                 if (predicate.test(classItem)) {
73                     imports.add(Import(classItem))
74                 }
75             }
76         }
77 
78         // Next only keep those that are present in any docs; those are the only ones
79         // we need to import
80         if (imports.isNotEmpty()) {
81             return filterImports(imports, predicate)
82         }
83 
84         return emptyList()
85     }
86 
87     /**
88      * The [LineMap] used to map positions in the source file into line numbers.
89      *
90      * Created lazily as it can be expensive to create.
91      */
92     private val lineMap by
<lambda>null93         lazy(LazyThreadSafetyMode.NONE) { LineMap.create(compUnit.source().source()) }
94 
95     /**
96      * Get the line number for [position] which was retrieved from
97      * [com.google.turbine.tree.Tree.position].
98      */
lineForPositionnull99     fun lineForPosition(position: Int) = lineMap.lineNumber(position)
100 }
101