All Downloads are FREE. Search and download functionalities are using the official Maven repository.

.kotlin.kotlin-compiler.1.3.11.source-code.JavacWrapper.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * 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,
        kotlinFiles: Collection,
        arguments: Array?,
        jvmClasspathRoots: List,
        bootClasspath: List?,
        sourcePath: List?,
        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?) = 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 = 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()) { unit ->
                unit.packageName?.toString()?.let { packageName ->
                    TreeBasedPackage(packageName, this, unit)
                } ?: TreeBasedPackage("", 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()
    private val symbolBasedClassesCache = hashMapOf()

    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 =
            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 =
            packageSourceAnnotations[fqName] ?: emptyList()

    fun findClassesFromPackage(fqName: FqName): List =
            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 =
            treeBasedJavaClasses
                    .filterKeys { it.packageFqName == fqName }
                    .mapTo(hashSetOf()) { it.value.name.asString() } +
            elements.getPackageElement(fqName.asString())
                    ?.members_field
                    ?.elements
                    ?.filterIsInstance()
                    ?.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  Iterable.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 }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy