blob: ab26af390e5e46761132015677be5d97897c052e [file] [log] [blame]
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.javac
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.search.EverythingGlobalScope
import com.intellij.psi.search.GlobalSearchScope
import com.sun.source.tree.CompilationUnitTree
import com.sun.tools.javac.code.Flags
import com.sun.tools.javac.code.Symbol
import com.sun.tools.javac.code.Symtab
import com.sun.tools.javac.file.JavacFileManager
import com.sun.tools.javac.jvm.ClassReader
import com.sun.tools.javac.main.JavaCompiler
import com.sun.tools.javac.model.JavacElements
import com.sun.tools.javac.model.JavacTypes
import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.util.Context
import com.sun.tools.javac.util.Log
import com.sun.tools.javac.util.Names
import com.sun.tools.javac.util.Options
import org.jetbrains.kotlin.javac.resolve.ClassifierResolver
import org.jetbrains.kotlin.javac.resolve.IdentifierResolver
import org.jetbrains.kotlin.javac.resolve.KotlinClassifiersCache
import org.jetbrains.kotlin.javac.resolve.classId
import org.jetbrains.kotlin.javac.wrappers.symbols.SymbolBasedClass
import org.jetbrains.kotlin.javac.wrappers.symbols.SymbolBasedClassifierType
import org.jetbrains.kotlin.javac.wrappers.symbols.SymbolBasedPackage
import org.jetbrains.kotlin.javac.wrappers.trees.*
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.*
import org.jetbrains.kotlin.psi.KtFile
import java.io.Closeable
import java.io.File
import javax.lang.model.element.Element
import javax.lang.model.type.TypeMirror
import javax.tools.JavaFileManager
import javax.tools.JavaFileObject
import javax.tools.StandardLocation
import com.sun.tools.javac.util.List as JavacList
class JavacWrapper(
javaFiles: Collection<File>,
kotlinFiles: Collection<KtFile>,
arguments: Array<String>?,
jvmClasspathRoots: List<File>,
bootClasspath: List<File>?,
sourcePath: List<File>?,
val kotlinResolver: JavacWrapperKotlinResolver,
private val compileJava: Boolean,
private val outputDirectory: File?,
private val context: Context
) : Closeable {
private val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL)!!
private val jarFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL)!!
companion object {
fun getInstance(project: Project): JavacWrapper = ServiceManager.getService(project, JavacWrapper::class.java)
}
private fun createCommonClassifierType(classId: ClassId) =
findClassInSymbols(classId)?.let {
SymbolBasedClassifierType(it.element.asType(), this)
}
val JAVA_LANG_OBJECT by lazy {
createCommonClassifierType(classId("java.lang", "Object"))
}
val JAVA_LANG_ENUM by lazy {
findClassInSymbols(classId("java.lang", "Enum"))
}
val JAVA_LANG_ANNOTATION_ANNOTATION by lazy {
createCommonClassifierType(classId("java.lang.annotation", "Annotation"))
}
init {
Options.instance(context).let { options ->
JavacOptionsMapper.setUTF8Encoding(options)
arguments?.toList()?.let { JavacOptionsMapper.map(options, it) }
}
}
private val javac = object : JavaCompiler(context) {
override fun parseFiles(files: Iterable<JavaFileObject>?) = compilationUnits
}
private val fileManager = context[JavaFileManager::class.java] as JavacFileManager
init {
// keep javadoc comments
javac.keepComments = true
// use rt.jar instead of lib/ct.sym
fileManager.setSymbolFileEnabled(false)
bootClasspath?.let {
val cp = fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH) + jvmClasspathRoots
fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, it)
fileManager.setLocation(StandardLocation.CLASS_PATH, cp)
} ?: fileManager.setLocation(StandardLocation.CLASS_PATH, jvmClasspathRoots)
sourcePath?.let {
fileManager.setLocation(StandardLocation.SOURCE_PATH, sourcePath)
}
}
private val names = Names.instance(context)
private val symbols = Symtab.instance(context)
private val elements = JavacElements.instance(context)
private val types = JavacTypes.instance(context)
private val fileObjects = fileManager.getJavaFileObjectsFromFiles(javaFiles).toJavacList()
private val compilationUnits: JavacList<JCTree.JCCompilationUnit> = fileObjects.map(javac::parse).toJavacList()
private val treeBasedJavaClasses = compilationUnits.flatMap { unit ->
unit.typeDecls.map { classDeclaration ->
val packageName = unit.packageName?.toString() ?: ""
val className = (classDeclaration as JCTree.JCClassDecl).simpleName.toString()
val classId = classId(packageName, className)
classId to TreeBasedClass(classDeclaration, unit, this, classId, null)
}
}.toMap()
private val javaPackages = compilationUnits
.mapTo(hashSetOf<TreeBasedPackage>()) { unit ->
unit.packageName?.toString()?.let { packageName ->
TreeBasedPackage(packageName, this, unit)
} ?: TreeBasedPackage("<root>", this, unit)
}
.associateBy(TreeBasedPackage::fqName)
private val packageSourceAnnotations = compilationUnits
.filter {
it.sourceFile.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE) &&
it.packageName != null
}.associateBy({ FqName(it.packageName!!.toString()) }) { compilationUnit ->
compilationUnit.packageAnnotations
}
private val classifierResolver = ClassifierResolver(this)
private val identifierResolver = IdentifierResolver(this)
private val kotlinClassifiersCache by lazy { KotlinClassifiersCache(if (javaFiles.isNotEmpty()) kotlinFiles else emptyList(), this) }
private val symbolBasedPackagesCache = hashMapOf<String, SymbolBasedPackage?>()
private val symbolBasedClassesCache = hashMapOf<ClassId, SymbolBasedClass>()
fun compile(outDir: File? = null): Boolean = with(javac) {
if (!compileJava) return true
if (errorCount() > 0) return false
val javaFilesNumber = fileObjects.length()
if (javaFilesNumber == 0) return true
fileManager.setClassPathForCompilation(outDir)
context.get(Log.outKey)?.println("Compiling $javaFilesNumber Java source files" +
" to [${fileManager.getLocation(StandardLocation.CLASS_OUTPUT)?.firstOrNull()?.path}]")
compile(fileObjects)
errorCount() == 0
}
override fun close() {
fileManager.close()
javac.close()
}
fun findClass(classId: ClassId, scope: GlobalSearchScope = EverythingGlobalScope()): JavaClass? {
if (classId.isNestedClass) {
val pathSegments = classId.relativeClassName.pathSegments().map { it.asString() }
val outerClassId = ClassId(classId.packageFqName, Name.identifier(pathSegments.first()))
var outerClass = findClass(outerClassId, scope) ?: return null
pathSegments.drop(1).forEach {
outerClass = outerClass.findInnerClass(Name.identifier(it)) ?: return null
}
return outerClass
}
treeBasedJavaClasses[classId]?.let { javaClass ->
javaClass.virtualFile?.let { if (it in scope) return javaClass }
}
if (symbolBasedClassesCache.containsKey(classId)) {
val javaClass = symbolBasedClassesCache[classId]
javaClass?.virtualFile?.let { file ->
if (file in scope) return javaClass
}
}
findPackageInSymbols(classId.packageFqName.asString())?.let {
(it.element as Symbol.PackageSymbol).findClass(classId)?.let { javaClass ->
javaClass.virtualFile?.let { file ->
if (file in scope) return javaClass
}
}
}
return null
}
fun findPackage(fqName: FqName, scope: GlobalSearchScope = EverythingGlobalScope()): JavaPackage? {
javaPackages[fqName]?.let { javaPackage ->
javaPackage.virtualFile?.let { file ->
if (file in scope) return javaPackage
}
}
return findPackageInSymbols(fqName.asString())
}
fun findSubPackages(fqName: FqName): List<JavaPackage> =
symbols.packages
.filterKeys { it.toString().startsWith("$fqName.") }
.map { SymbolBasedPackage(it.value, this) } +
javaPackages
.filterKeys { it.isSubpackageOf(fqName) && it != fqName }
.map { it.value }
fun getPackageAnnotationsFromSources(fqName: FqName): List<JCTree.JCAnnotation> =
packageSourceAnnotations[fqName] ?: emptyList()
fun findClassesFromPackage(fqName: FqName): List<JavaClass> =
treeBasedJavaClasses
.filterKeys { it.packageFqName == fqName }
.map { treeBasedJavaClasses[it.key]!! } +
elements.getPackageElement(fqName.asString())
?.members()
?.elements
?.filterIsInstance(Symbol.ClassSymbol::class.java)
?.map { SymbolBasedClass(it, this, null, it.classfile) }
.orEmpty()
fun knownClassNamesInPackage(fqName: FqName): Set<String> =
treeBasedJavaClasses
.filterKeys { it.packageFqName == fqName }
.mapTo(hashSetOf()) { it.value.name.asString() } +
elements.getPackageElement(fqName.asString())
?.members_field
?.elements
?.filterIsInstance<Symbol.ClassSymbol>()
?.map { it.name.toString() }
.orEmpty()
fun getKotlinClassifier(classId: ClassId): JavaClass? =
kotlinClassifiersCache.getKotlinClassifier(classId)
fun isDeprecated(element: Element) = elements.isDeprecated(element)
fun isDeprecated(typeMirror: TypeMirror) = isDeprecated(types.asElement(typeMirror))
fun resolve(tree: JCTree, compilationUnit: CompilationUnitTree, containingElement: JavaElement): JavaClassifier? =
classifierResolver.resolve(tree, compilationUnit, containingElement)
fun resolveField(tree: JCTree, compilationUnit: CompilationUnitTree, containingClass: JavaClass): JavaField? =
identifierResolver.resolve(tree, compilationUnit, containingClass)
fun toVirtualFile(javaFileObject: JavaFileObject): VirtualFile? =
javaFileObject.toUri().let { uri ->
if (uri.scheme == "jar") {
jarFileSystem.findFileByPath(uri.schemeSpecificPart.substring("file:".length))
}
else {
localFileSystem.findFileByPath(uri.schemeSpecificPart)
}
}
fun hasKotlinPackage(fqName: FqName) =
if (kotlinClassifiersCache.hasPackage(fqName)) {
fqName
}
else {
null
}
fun isDeprecatedInJavaDoc(tree: JCTree, compilationUnit: CompilationUnitTree) =
(compilationUnit as JCTree.JCCompilationUnit).docComments?.getCommentTree(tree)?.comment?.isDeprecated == true
private inline fun <reified T> Iterable<T>.toJavacList() = JavacList.from(this)
private fun findClassInSymbols(classId: ClassId): SymbolBasedClass? =
elements.getTypeElement(classId.asSingleFqName().asString())?.let { symbol ->
SymbolBasedClass(symbol, this, classId, symbol.classfile)
}
private fun findPackageInSymbols(fqName: String): SymbolBasedPackage? {
if (symbolBasedPackagesCache.containsKey(fqName)) return symbolBasedPackagesCache[fqName]
elements.getPackageElement(fqName)?.let { symbol ->
SymbolBasedPackage(symbol, this)
}.let { symbolBasedPackage ->
symbolBasedPackagesCache[fqName] = symbolBasedPackage
return symbolBasedPackage
}
}
private fun JavacFileManager.setClassPathForCompilation(outDir: File?) = apply {
(outDir ?: outputDirectory)?.let { outputDir ->
outputDir.mkdirs()
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, listOf(outputDir))
}
val reader = ClassReader.instance(context)
val names = Names.instance(context)
val outDirName = getLocation(StandardLocation.CLASS_OUTPUT)?.firstOrNull()?.path ?: ""
list(StandardLocation.CLASS_OUTPUT, "", setOf(JavaFileObject.Kind.CLASS), true)
.forEach { fileObject ->
val fqName = fileObject.name
.substringAfter(outDirName)
.substringBefore(".class")
.replace(File.separator, ".")
.let { className ->
if (className.startsWith(".")) className.substring(1) else className
}.let(names::fromString)
symbols.classes[fqName]?.let { symbols.classes[fqName] = null }
val symbol = reader.enterClass(fqName, fileObject)
(elements.getPackageOf(symbol) as? Symbol.PackageSymbol)?.let { packageSymbol ->
packageSymbol.members_field.enter(symbol)
packageSymbol.flags_field = packageSymbol.flags_field or Flags.EXISTS.toLong()
}
}
}
private fun Symbol.PackageSymbol.findClass(classId: ClassId): SymbolBasedClass? {
val name = classId.relativeClassName.asString()
val nameParts = name.replace("$", ".").split(".")
var symbol = members_field?.getElementsByName(names.fromString(nameParts.first()))
?.firstOrNull() as? Symbol.ClassSymbol ?: return null
if (nameParts.size > 1) {
symbol.complete()
for (it in nameParts.drop(1)) {
symbol = symbol.members_field?.getElementsByName(names.fromString(it))?.firstOrNull() as? Symbol.ClassSymbol ?: return null
symbol.complete()
}
}
return symbol.let { SymbolBasedClass(it, this@JavacWrapper, classId, it.classfile) }
.apply { symbolBasedClassesCache[classId] = this }
}
}