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

org.jetbrains.kotlin.cli.jvm.compiler.KotlinCliJavaFileManagerImpl.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2015 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.cli.jvm.compiler

import com.intellij.core.CoreJavaFileManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassOwner
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiPackage
import com.intellij.psi.impl.file.PsiPackageImpl
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
import org.jetbrains.kotlin.util.PerformanceCounter
import java.util.ArrayList
import kotlin.properties.Delegates

public class KotlinCliJavaFileManagerImpl(private val myPsiManager: PsiManager)
: CoreJavaFileManager(myPsiManager), KotlinCliJavaFileManager {

    private val perfCounter = PerformanceCounter.create("Find Java class")
    private var index: JvmDependenciesIndex by Delegates.notNull()

    public fun initIndex(packagesCache: JvmDependenciesIndex) {
        this.index = packagesCache
    }

    public override fun findClass(classId: ClassId, searchScope: GlobalSearchScope): PsiClass? {
        return perfCounter.time {
            val classNameWithInnerClasses = classId.getRelativeClassName().asString()
            index.findClass(classId) { dir, type ->
                findClassGivenPackage(searchScope, dir, classNameWithInnerClasses, type)
            }
        }
    }

    override fun findClass(qName: String, scope: GlobalSearchScope): PsiClass? {
        // this method is called from IDEA to resolve dependencies in Java code
        // which supposedly shouldn't have errors so the dependencies exist in general
        // Most classes are top level classes so we will try to find them fast
        // but we must sometimes fallback to support finding inner/nested classes
        return qName.toSafeTopLevelClassId()?.let { classId -> findClass(classId, scope) }
               ?: super.findClass(qName, scope)
    }

    override fun findClasses(qName: String, scope: GlobalSearchScope): Array {
        return perfCounter.time {
            val classIdAsTopLevelClass = qName.toSafeTopLevelClassId() ?: return@time super.findClasses(qName, scope)

            val result = ArrayList()
            val classNameWithInnerClasses = classIdAsTopLevelClass.getRelativeClassName().asString()
            index.traverseDirectoriesInPackage(classIdAsTopLevelClass.getPackageFqName()) { dir, rootType ->
                val psiClass = findClassGivenPackage(scope, dir, classNameWithInnerClasses, rootType)
                if (psiClass != null) {
                    result.add(psiClass)
                }
                // traverse all
                true
            }
            if (result.isEmpty()) {
                super.findClasses(qName, scope)
            }
            else {
                result.toTypedArray()
            }
        }
    }

    override fun findPackage(packageName: String): PsiPackage? {
        var found = false
        val packageFqName = packageName.toSafeFqName() ?: return null
        index.traverseDirectoriesInPackage(packageFqName) { _, __ ->
            found = true
            //abort on first found
            false
        }
        if (found) {
            return PsiPackageImpl(myPsiManager, packageName)
        }
        return null
    }

    private fun findClassGivenPackage(
            scope: GlobalSearchScope, packageDir: VirtualFile,
            classNameWithInnerClasses: String, rootType: JavaRoot.RootType
    ): PsiClass? {
        val topLevelClassName = classNameWithInnerClasses.substringBefore('.')

        val vFile = when (rootType) {
            JavaRoot.RootType.BINARY -> packageDir.findChild("$topLevelClassName.class")
            JavaRoot.RootType.SOURCE -> packageDir.findChild("$topLevelClassName.java")
        } ?: return null

        if (!vFile.isValid()) {
            LOG.error("Invalid child of valid parent: ${vFile.getPath()}; ${packageDir.isValid()} path=${packageDir.getPath()}")
            return null
        }
        if (vFile !in scope) {
            return null
        }

        val file = myPsiManager.findFile(vFile) as? PsiClassOwner ?: return null
        return findClassInPsiFile(classNameWithInnerClasses, file)
    }

    companion object {
        private val LOG = Logger.getInstance(javaClass())

        private fun findClassInPsiFile(classNameWithInnerClassesDotSeparated: String, file: PsiClassOwner): PsiClass? {
            for (topLevelClass in file.getClasses()) {
                val candidate = findClassByTopLevelClass(classNameWithInnerClassesDotSeparated, topLevelClass)
                if (candidate != null) {
                    return candidate
                }
            }
            return null
        }

        private fun findClassByTopLevelClass(className: String, topLevelClass: PsiClass): PsiClass? {
            if (className.indexOf('.') < 0) {
                return if (className == topLevelClass.getName()) topLevelClass else null
            }

            val segments = StringUtil.split(className, ".").iterator()
            if (!segments.hasNext() || segments.next() != topLevelClass.getName()) {
                return null
            }
            var curClass = topLevelClass
            while (segments.hasNext()) {
                val innerClassName = segments.next()
                val innerClass = curClass.findInnerClassByName(innerClassName, false)
                if (innerClass == null) {
                    return null
                }
                curClass = innerClass
            }
            return curClass
        }
    }
}

// a sad workaround to avoid throwing exception when called from inside IDEA code
private fun  safely(compute: () -> T): T? = try {
    compute()
}
catch (e: IllegalArgumentException) {
    null
}
catch (e: AssertionError) {
    null
}

private fun String.toSafeFqName(): FqName? = safely { FqName(this) }
private fun String.toSafeTopLevelClassId(): ClassId? = safely { ClassId.topLevel(FqName(this)) }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy