• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.psi
18 
19 import com.android.tools.lint.UastEnvironment
20 import com.android.tools.metalava.model.Codebase
21 import com.android.tools.metalava.model.ModelOptions
22 import com.android.tools.metalava.model.source.EnvironmentManager
23 import com.android.tools.metalava.model.source.SourceParser
24 import com.intellij.core.CoreApplicationEnvironment
25 import com.intellij.openapi.diagnostic.DefaultLogger
26 import com.intellij.openapi.util.Disposer
27 import com.intellij.pom.java.LanguageLevel
28 import com.intellij.psi.javadoc.CustomJavadocTagProvider
29 import com.intellij.psi.javadoc.JavadocTagInfo
30 import java.io.File
31 import kotlin.io.path.createTempDirectory
32 import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
33 import org.jetbrains.kotlin.config.CommonConfigurationKeys
34 
35 /** Manages the [UastEnvironment] objects created when processing sources. */
36 class PsiEnvironmentManager(
37     private val disableStderrDumping: Boolean = false,
38     private val forTesting: Boolean = false,
39 ) : EnvironmentManager {
40 
41     /**
42      * True if this is responsible for creating and this owning the application environment.
43      *
44      * This is needed to allow a [PsiEnvironmentManager] to be created while using another
45      * [PsiEnvironmentManager], e.g. in tests that require two [Codebase]s.
46      */
47     private val ownApplicationEnvironment: Boolean =
48         KotlinCoreEnvironment.applicationEnvironment == null
49 
50     /**
51      * An empty directory, used when it is necessary to create an environment without any source.
52      * Simply providing an empty list of source roots will cause it to use the current working
53      * directory.
54      */
<lambda>null55     internal val emptyDir by lazy {
56         val path = createTempDirectory()
57         val file = path.toFile()
58         file.deleteOnExit()
59         file
60     }
61 
62     /**
63      * Determines whether the manager has been closed. Used to prevent creating new environments
64      * after the manager has closed.
65      */
66     private var closed = false
67 
68     /** The list of available environments. */
69     private val uastEnvironments = mutableListOf<UastEnvironment>()
70 
71     init {
72         if (forTesting) {
73             System.setProperty("java.awt.headless", "true")
74             Disposer.setDebugMode(true)
75         }
76     }
77 
78     /**
79      * Create a [UastEnvironment] with the supplied configuration.
80      *
81      * @throws IllegalStateException if this manager has been closed.
82      */
createEnvironmentnull83     internal fun createEnvironment(config: UastEnvironment.Configuration): UastEnvironment {
84         if (closed) {
85             throw IllegalStateException("PsiEnvironmentManager is closed")
86         }
87         ensurePsiFileCapacity()
88 
89         // Note: the Kotlin module name affects the naming of certain synthetic methods.
90         config.kotlinCompilerConfig.put(
91             CommonConfigurationKeys.MODULE_NAME,
92             METALAVA_SYNTHETIC_SUFFIX
93         )
94 
95         val environment = UastEnvironment.create(config)
96         uastEnvironments.add(environment)
97 
98         if (disableStderrDumping) {
99             DefaultLogger.disableStderrDumping(environment.ideaProject)
100         }
101 
102         // Missing service needed in metalava but not in lint: javadoc handling
103         environment.ideaProject.registerService(
104             com.intellij.psi.javadoc.JavadocManager::class.java,
105             com.intellij.psi.impl.source.javadoc.JavadocManagerImpl::class.java
106         )
107         CoreApplicationEnvironment.registerExtensionPoint(
108             environment.ideaProject.extensionArea,
109             JavadocTagInfo.EP_NAME,
110             JavadocTagInfo::class.java
111         )
112         CoreApplicationEnvironment.registerApplicationExtensionPoint(
113             CustomJavadocTagProvider.EP_NAME,
114             CustomJavadocTagProvider::class.java
115         )
116 
117         return environment
118     }
119 
ensurePsiFileCapacitynull120     private fun ensurePsiFileCapacity() {
121         val fileSize = System.getProperty("idea.max.intellisense.filesize")
122         if (fileSize == null) {
123             // Ensure we can handle large compilation units like android.R
124             System.setProperty("idea.max.intellisense.filesize", "100000")
125         }
126     }
127 
createSourceParsernull128     override fun createSourceParser(
129         codebaseConfig: Codebase.Config,
130         javaLanguageLevel: String,
131         kotlinLanguageLevel: String,
132         modelOptions: ModelOptions,
133         allowReadingComments: Boolean,
134         jdkHome: File?,
135     ): SourceParser {
136         return PsiSourceParser(
137             psiEnvironmentManager = this,
138             codebaseConfig = codebaseConfig,
139             javaLanguageLevel = javaLanguageLevelFromString(javaLanguageLevel),
140             kotlinLanguageLevel = kotlinLanguageVersionSettings(kotlinLanguageLevel),
141             useK2Uast = modelOptions[PsiModelOptions.useK2Uast],
142             allowReadingComments = allowReadingComments,
143             jdkHome = jdkHome,
144         )
145     }
146 
closenull147     override fun close() {
148         closed = true
149 
150         // Codebase.dispose() is not consistently called, so we dispose the environments here too.
151         for (env in uastEnvironments) {
152             if (!env.ideaProject.isDisposed) {
153                 env.dispose()
154             }
155         }
156         uastEnvironments.clear()
157 
158         // Only dispose of the application environment if this object was responsible for creating.
159         // If it was not then there is no point in checking to make sure that [Disposer] is empty
160         // because it will include items that have not yet been disposed of by the
161         // [PsiEnvironmentManager] which does own the application environment.
162         if (ownApplicationEnvironment) {
163             UastEnvironment.disposeApplicationEnvironment()
164             if (forTesting) {
165                 Disposer.assertIsEmpty(true)
166             }
167         }
168     }
169 
170     companion object {
javaLanguageLevelFromStringnull171         fun javaLanguageLevelFromString(value: String): LanguageLevel {
172             val level = LanguageLevel.parse(value)
173             when {
174                 level == null ->
175                     throw IllegalStateException(
176                         "$value is not a valid or supported Java language level"
177                     )
178                 level.isLessThan(LanguageLevel.JDK_1_7) ->
179                     throw IllegalStateException("$value must be at least 1.7")
180                 else -> return level
181             }
182         }
183     }
184 }
185 
186 private const val METALAVA_SYNTHETIC_SUFFIX = "metalava_module"
187