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.psi
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.DefaultModifierList
21 import com.android.tools.metalava.model.PackageItem
22 import com.android.tools.metalava.model.VisibilityLevel
23 import com.intellij.psi.PsiPackage
24 
25 class PsiPackageItem
26 internal constructor(
27     codebase: PsiBasedCodebase,
28     private val psiPackage: PsiPackage,
29     private val qualifiedName: String,
30     modifiers: DefaultModifierList,
31     documentation: String,
32     override val overviewDocumentation: String?,
33     /** True if this package is from the classpath (dependencies). Exposed in [isFromClassPath]. */
34     private val fromClassPath: Boolean
35 ) :
36     PsiItem(
37         codebase = codebase,
38         modifiers = modifiers,
39         documentation = documentation,
40         element = psiPackage
41     ),
42     PackageItem {
43 
44     // Note - top level classes only
45     private val classes: MutableList<ClassItem> = mutableListOf()
46 
topLevelClassesnull47     override fun topLevelClasses(): Sequence<ClassItem> =
48         classes.toList().asSequence().filter { it.isTopLevelClass() }
49 
50     lateinit var containingPackageField: PsiPackageItem
51 
containingClassnull52     override fun containingClass(): ClassItem? = null
53 
54     override fun psi() = psiPackage
55 
56     override fun containingPackage(): PackageItem? {
57         return if (qualifiedName.isEmpty()) null
58         else {
59             if (!::containingPackageField.isInitialized) {
60                 var parentPackage = qualifiedName
61                 while (true) {
62                     val index = parentPackage.lastIndexOf('.')
63                     if (index == -1) {
64                         containingPackageField = codebase.findPackage("")!!
65                         return containingPackageField
66                     }
67                     parentPackage = parentPackage.substring(0, index)
68                     val pkg = codebase.findPackage(parentPackage)
69                     if (pkg != null) {
70                         containingPackageField = pkg
71                         return pkg
72                     }
73                 }
74 
75                 @Suppress("UNREACHABLE_CODE") null
76             } else {
77                 containingPackageField
78             }
79         }
80     }
81 
addClassnull82     fun addClass(cls: PsiClassItem) {
83         if (!cls.isTopLevelClass()) {
84             // TODO: Stash in a list somewhere to make allClasses() faster?
85             return
86         }
87 
88         /*
89         // Temp debugging:
90         val q = cls.qualifiedName()
91         for (c in classes) {
92             if (q == c.qualifiedName()) {
93                 assert(false, { "Unexpectedly found class $q already listed in $this" })
94                 return
95             }
96         }
97         */
98 
99         classes.add(cls)
100         cls.containingPackage = this
101     }
102 
addClassesnull103     fun addClasses(classList: List<PsiClassItem>) {
104         for (cls in classList) {
105             addClass(cls)
106         }
107     }
108 
qualifiedNamenull109     override fun qualifiedName(): String = qualifiedName
110 
111     override fun equals(other: Any?): Boolean {
112         if (this === other) {
113             return true
114         }
115         return other is PackageItem && qualifiedName == other.qualifiedName()
116     }
117 
hashCodenull118     override fun hashCode(): Int = qualifiedName.hashCode()
119 
120     override fun isFromClassPath(): Boolean = fromClassPath
121 
122     companion object {
123         fun create(
124             codebase: PsiBasedCodebase,
125             psiPackage: PsiPackage,
126             extraDocs: String?,
127             overviewHtml: String?,
128             fromClassPath: Boolean,
129         ): PsiPackageItem {
130             val commentText =
131                 javadoc(psiPackage, codebase.allowReadingComments) +
132                     if (extraDocs != null) "\n$extraDocs" else ""
133             val modifiers = modifiers(codebase, psiPackage, commentText)
134             if (modifiers.isPackagePrivate()) {
135                 // packages are always public (if not hidden explicitly with private)
136                 modifiers.setVisibilityLevel(VisibilityLevel.PUBLIC)
137             }
138             val qualifiedName = psiPackage.qualifiedName
139 
140             val pkg =
141                 PsiPackageItem(
142                     codebase = codebase,
143                     psiPackage = psiPackage,
144                     qualifiedName = qualifiedName,
145                     documentation = commentText,
146                     overviewDocumentation = overviewHtml,
147                     modifiers = modifiers,
148                     fromClassPath = fromClassPath
149                 )
150             return pkg
151         }
152     }
153 }
154