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