javac-wrapper: refactoring, fixes and tests

This commit is contained in:
baratynskiy
2017-06-27 17:24:10 +03:00
committed by Alexander Baratynskiy
parent 8494e54608
commit 01883a41cb
382 changed files with 42897 additions and 688 deletions
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.javac
import com.sun.tools.javac.main.Option
import com.sun.tools.javac.util.Options
import java.util.regex.Pattern
@@ -25,6 +26,8 @@ object JavacOptionsMapper {
arguments.forEach { options.putOption(it) }
}
fun setUTF8Encoding(options: Options) = options.put(Option.ENCODING, "UTF8")
private val optionPattern = Pattern.compile("\\s+")
private fun Options.putOption(option: String) =
@@ -35,6 +35,7 @@ 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.DCTree
import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.util.Context
import com.sun.tools.javac.util.Log
@@ -48,10 +49,7 @@ import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaClassifier
import org.jetbrains.kotlin.load.java.structure.JavaPackage
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.isSubpackageOf
import org.jetbrains.kotlin.name.parentOrNull
import org.jetbrains.kotlin.name.*
import org.jetbrains.kotlin.psi.KtFile
import java.io.Closeable
import java.io.File
@@ -67,6 +65,9 @@ class JavacWrapper(
kotlinFiles: Collection<KtFile>,
arguments: Array<String>?,
jvmClasspathRoots: List<File>,
bootClasspath: List<File>?,
sourcePath: List<File>?,
val kotlinSupertypeResolver: KotlinSupertypeResolver,
private val compileJava: Boolean,
private val outputDirectory: File?,
private val context: Context
@@ -88,7 +89,7 @@ class JavacWrapper(
}
val JAVA_LANG_ENUM by lazy {
createCommonClassifierType(CommonClassNames.JAVA_LANG_ENUM)
findClassInSymbols(CommonClassNames.JAVA_LANG_ENUM)
}
val JAVA_LANG_ANNOTATION_ANNOTATION by lazy {
@@ -96,7 +97,10 @@ class JavacWrapper(
}
init {
arguments?.toList()?.let { JavacOptionsMapper.map(Options.instance(context), it) }
Options.instance(context).let { options ->
JavacOptionsMapper.setUTF8Encoding(options)
arguments?.toList()?.let { JavacOptionsMapper.map(options, it) }
}
}
private val javac = object : JavaCompiler(context) {
@@ -106,9 +110,18 @@ class JavacWrapper(
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)
fileManager.setLocation(StandardLocation.CLASS_PATH, jvmClasspathRoots)
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)
@@ -119,25 +132,36 @@ class JavacWrapper(
private val fileObjects = fileManager.getJavaFileObjectsFromFiles(javaFiles).toJavacList()
private val compilationUnits: JavacList<JCTree.JCCompilationUnit> = fileObjects.map(javac::parse).toJavacList()
private val javaClasses = compilationUnits
.flatMap { unit ->
unit.typeDecls.flatMap { classDecl ->
TreeBasedClass(classDecl as JCTree.JCClassDecl,
trees.getPath(unit, classDecl),
this,
unit.sourceFile).withInnerClasses()
}
}
.associateBy(JavaClass::fqName)
private val treeBasedJavaClasses = hashMapOf<ClassId, TreeBasedClass>()
private val javaClassesAssociatedByClassId =
javaClasses.values.associateBy { it.computeClassId() }
private val javaClassDeclarations = 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 Pair(classDeclaration, unit)
}
}.toMap()
private fun getTreeBasedClass(classId: ClassId): TreeBasedClass? {
if (treeBasedJavaClasses.containsKey(classId)) {
return treeBasedJavaClasses[classId]
}
val (classDeclaration, unit) = javaClassDeclarations[classId] ?: return null
val treeBasedClass = TreeBasedClass(classDeclaration,
trees.getPath(unit, classDeclaration),
this,
unit.sourceFile)
return treeBasedClass.apply { treeBasedJavaClasses[classId] = this }
}
private val javaPackages = compilationUnits
.mapNotNullTo(hashSetOf()) { unit ->
.mapTo(hashSetOf<TreeBasedPackage>()) { unit ->
unit.packageName?.toString()?.let { packageName ->
TreeBasedPackage(packageName, this, unit.sourcefile)
}
} ?: TreeBasedPackage("<root>", this, unit.sourcefile)
}
.associateBy(TreeBasedPackage::fqName)
@@ -146,12 +170,11 @@ class JavacWrapper(
it.sourceFile.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE) &&
it.packageName != null
}.associateBy({ FqName(it.packageName!!.toString()) }) { compilationUnit ->
compilationUnit.packageAnnotations.map { TreeBasedAnnotation(it, compilationUnit, this) }
}
compilationUnit.packageAnnotations.map { TreeBasedAnnotation(it, getTreePath(it, compilationUnit), this) }
}
val classifierResolver = ClassifierResolver(this)
private val kotlinClassifiersCache = KotlinClassifiersCache(if (javaFiles.isNotEmpty()) kotlinFiles else emptyList(), this)
private val treePathResolverCache = TreePathResolverCache(this)
private val symbolBasedClassesCache = hashMapOf<String, SymbolBasedClass?>()
private val symbolBasedPackagesCache = hashMapOf<String, SymbolBasedPackage?>()
fun compile(outDir: File? = null): Boolean = with(javac) {
@@ -173,20 +196,20 @@ class JavacWrapper(
javac.close()
}
fun findClass(fqName: FqName, scope: GlobalSearchScope = EverythingGlobalScope()): JavaClass? {
javaClasses[fqName]?.let { javaClass ->
javaClass.virtualFile?.let { if (it in scope) return javaClass }
}
findClassInSymbols(fqName.asString())?.let { javaClass ->
javaClass.virtualFile?.let { if (it in scope) return javaClass }
}
return null
}
fun findClass(classId: ClassId, scope: GlobalSearchScope = EverythingGlobalScope()): JavaClass? {
javaClassesAssociatedByClassId[classId]?.let { 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
}
getTreeBasedClass(classId)?.let { javaClass ->
javaClass.virtualFile?.let { if (it in scope) return javaClass }
}
@@ -202,7 +225,7 @@ class JavacWrapper(
return null
}
fun findPackage(fqName: FqName, scope: GlobalSearchScope): JavaPackage? {
fun findPackage(fqName: FqName, scope: GlobalSearchScope = EverythingGlobalScope()): JavaPackage? {
javaPackages[fqName]?.let { javaPackage ->
javaPackage.virtualFile?.let { file ->
if (file in scope) return javaPackage
@@ -224,9 +247,9 @@ class JavacWrapper(
packageSourceAnnotations[fqName] ?: emptyList()
fun findClassesFromPackage(fqName: FqName): List<JavaClass> =
javaClasses
.filterKeys { it?.parentOrNull() == fqName }
.flatMap { it.value.withInnerClasses() } +
javaClassDeclarations
.filterKeys { it.packageFqName == fqName }
.map { getTreeBasedClass(it.key)!! } +
elements.getPackageElement(fqName.asString())
?.members()
?.elements
@@ -235,8 +258,9 @@ class JavacWrapper(
.orEmpty()
fun knownClassNamesInPackage(fqName: FqName): Set<String> =
javaClasses.filterKeys { it?.parentOrNull() == fqName }
.mapTo(hashSetOf()) { it.value.name.asString() } +
javaClassDeclarations
.filterKeys { it.packageFqName == fqName }
.mapTo(hashSetOf()) { it.key.shortClassName.asString() } +
elements.getPackageElement(fqName.asString())
?.members_field
?.elements
@@ -247,15 +271,15 @@ class JavacWrapper(
fun getTreePath(tree: JCTree, compilationUnit: CompilationUnitTree): TreePath =
trees.getPath(compilationUnit, tree)
fun getKotlinClassifier(fqName: FqName): JavaClass? =
kotlinClassifiersCache.getKotlinClassifier(fqName)
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(treePath: TreePath): JavaClassifier? =
treePathResolverCache.resolve(treePath)
classifierResolver.resolve(treePath)
fun toVirtualFile(javaFileObject: JavaFileObject): VirtualFile? =
javaFileObject.toUri().let { uri ->
@@ -267,18 +291,23 @@ class JavacWrapper(
}
}
fun hasKotlinPackage(fqName: FqName) =
if (kotlinClassifiersCache.hasPackage(fqName)) {
fqName
}
else {
null
}
fun isDeprecatedInJavaDoc(treePath: TreePath) =
(trees.getDocCommentTree(treePath) as? DCTree.DCDocComment)?.comment?.isDeprecated ?: false
private inline fun <reified T> Iterable<T>.toJavacList() = JavacList.from(this)
private fun findClassInSymbols(fqName: String): SymbolBasedClass? {
if (symbolBasedClassesCache.containsKey(fqName)) return symbolBasedClassesCache[fqName]
elements.getTypeElement(fqName)?.let { symbol ->
SymbolBasedClass(symbol, this, symbol.classfile)
}.let { symbolBasedClass ->
symbolBasedClassesCache[fqName] = symbolBasedClass
return symbolBasedClass
}
}
private fun findClassInSymbols(fqName: String): SymbolBasedClass? =
elements.getTypeElement(fqName)?.let { symbol ->
SymbolBasedClass(symbol, this, symbol.classfile)
}
private fun findPackageInSymbols(fqName: String): SymbolBasedPackage? {
if (symbolBasedPackagesCache.containsKey(fqName)) return symbolBasedPackagesCache[fqName]
@@ -322,12 +351,9 @@ class JavacWrapper(
}
private fun TreeBasedClass.withInnerClasses(): List<TreeBasedClass> =
listOf(this) + innerClasses.values.flatMap { it.withInnerClasses() }
private fun Symbol.PackageSymbol.findClass(name: String): SymbolBasedClass? {
val nameParts = name.replace("$", ".").split(".")
var symbol = members_field.getElementsByName(names.fromString(nameParts.first()))
var symbol = members_field?.getElementsByName(names.fromString(nameParts.first()))
?.firstOrNull() as? Symbol.ClassSymbol ?: return null
if (nameParts.size > 1) {
symbol.complete()
@@ -340,4 +366,4 @@ class JavacWrapper(
return symbol.let { SymbolBasedClass(it, this@JavacWrapper, it.classfile) }
}
}
}
@@ -18,51 +18,73 @@ package org.jetbrains.kotlin.javac
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.SearchScope
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JavaVisibilities
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.javac.wrappers.trees.find
import org.jetbrains.kotlin.javac.wrappers.trees.findInner
import org.jetbrains.kotlin.javac.wrappers.trees.tryToResolveByFqName
import org.jetbrains.kotlin.javac.wrappers.trees.tryToResolveInJavaLang
import org.jetbrains.kotlin.load.java.structure.impl.VirtualFileBoundJavaClass
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
class KotlinClassifiersCache(sourceFiles: Collection<KtFile>,
private val javac: JavacWrapper) {
private val kotlinClasses: Map<FqName?, KtClassOrObject?> = sourceFiles.flatMap { ktFile ->
ktFile.collectDescendantsOfType<KtClassOrObject>().map { it.fqName to it } +
(ktFile.javaFileFacadeFqName to null)
}.toMap()
private val kotlinPackages = hashSetOf<FqName>()
private val kotlinClasses: Map<ClassId?, KtClassOrObject?> =
sourceFiles.flatMap { ktFile ->
kotlinPackages.add(ktFile.packageFqName)
ktFile.declarations
.filterIsInstance<KtClassOrObject>()
.map { it.computeClassId() to it }
}.toMap()
private val classifiers = hashMapOf<FqName, JavaClass>()
private val classifiers = hashMapOf<ClassId, JavaClass>()
fun getKotlinClassifier(fqName: FqName) = classifiers[fqName] ?: createClassifier(fqName)
fun getKotlinClassifier(classId: ClassId) = classifiers[classId] ?: createClassifier(classId)
private fun createClassifier(fqName: FqName): JavaClass? {
if (!kotlinClasses.containsKey(fqName)) return null
val kotlinClassifier = kotlinClasses[fqName] ?: return null
fun createMockKotlinClassifier(classifier: KtClassOrObject,
classId: ClassId) = MockKotlinClassifier(classId,
classifier,
this,
javac)
.apply { classifiers[classId] = this }
return MockKotlinClassifier(fqName,
kotlinClassifier,
kotlinClassifier.typeParameters.isNotEmpty(),
javac)
.apply { classifiers[fqName] = this }
fun hasPackage(packageFqName: FqName) = kotlinPackages.contains(packageFqName)
private fun createClassifier(classId: ClassId): JavaClass? {
if (classId.isNestedClass) {
classifiers[classId]?.let { return it }
val pathSegments = classId.relativeClassName.pathSegments().map { it.asString() }
val outerClassId = ClassId(classId.packageFqName, Name.identifier(pathSegments.first()))
var outerClass: JavaClass = kotlinClasses[outerClassId]?.let { createMockKotlinClassifier(it, outerClassId) } ?: return null
pathSegments.drop(1).forEach {
outerClass = outerClass.findInnerClass(Name.identifier(it)) ?: return null
}
return outerClass.apply { classifiers[classId] = this }
}
val kotlinClassifier = kotlinClasses[classId] ?: return null
return createMockKotlinClassifier(kotlinClassifier, classId)
}
}
class MockKotlinClassifier(override val fqName: FqName,
class MockKotlinClassifier(val classId: ClassId,
private val classOrObject: KtClassOrObject,
val hasTypeParameters: Boolean,
private val cache: KotlinClassifiersCache,
private val javac: JavacWrapper) : VirtualFileBoundJavaClass {
override val fqName: FqName
get() = classId.asSingleFqName()
override val isAbstract: Boolean
get() = throw UnsupportedOperationException("Should not be called")
@@ -73,32 +95,26 @@ class MockKotlinClassifier(override val fqName: FqName,
get() = throw UnsupportedOperationException("Should not be called")
override val visibility: Visibility
get() = throw UnsupportedOperationException("Should not be called")
get() = when (classOrObject.visibilityModifierType()) {
null, KtTokens.PUBLIC_KEYWORD -> Visibilities.PUBLIC
KtTokens.PRIVATE_KEYWORD -> Visibilities.PRIVATE
KtTokens.PROTECTED_KEYWORD -> Visibilities.PROTECTED
else -> JavaVisibilities.PACKAGE_VISIBILITY
}
override val typeParameters: List<JavaTypeParameter>
get() = throw UnsupportedOperationException("Should not be called")
override val supertypes: Collection<JavaClassifierType>
get() = classOrObject.superTypeListEntries
.map { superTypeListEntry ->
val userType = superTypeListEntry.typeAsUserType
arrayListOf<String>().apply {
userType?.referencedName?.let { add(it) }
var qualifier = userType?.qualifier
while (qualifier != null) {
qualifier.referencedName?.let { add(it) }
qualifier = qualifier.qualifier
}
}.reversed().joinToString(separator = ".") { it }
}
.mapNotNull { resolveSupertype(it, classOrObject, javac) }
get() = javac.kotlinSupertypeResolver.resolveSupertypes(classOrObject)
.mapNotNull { javac.getKotlinClassifier(it) ?: javac.findClass(it) }
.map { MockKotlinClassifierType(it) }
val innerClasses: Collection<JavaClass>
get() = classOrObject.declarations.filterIsInstance<KtClassOrObject>()
.mapNotNull { nestedClassOrObject ->
nestedClassOrObject.fqName?.let {
javac.getKotlinClassifier(it)
nestedClassOrObject.computeClassId()?.let {
cache.createMockKotlinClassifier(nestedClassOrObject, it)
}
}
@@ -124,7 +140,13 @@ class MockKotlinClassifier(override val fqName: FqName,
get() = throw UnsupportedOperationException("Should not be called")
override val fields: Collection<JavaField>
get() = throw UnsupportedOperationException("Should not be called")
get() = classOrObject.declarations
.filterIsInstance<KtProperty>()
.map(::MockKotlinField) + classOrObject.companionObjects.flatMap {
it.declarations
.filterIsInstance<KtProperty>()
.map(::MockKotlinField)
}
override val constructors: Collection<JavaConstructor>
get() = throw UnsupportedOperationException("Should not be called")
@@ -149,6 +171,12 @@ class MockKotlinClassifier(override val fqName: FqName,
override fun findInnerClass(name: Name) =
innerClasses.find { it.name == name }
val typeParametersNumber: Int
get() = classOrObject.typeParameters.size
val hasTypeParameters: Boolean
get() = typeParametersNumber > 0
}
class MockKotlinClassifierType(override val classifier: JavaClassifier) : JavaClassifierType {
@@ -176,88 +204,39 @@ class MockKotlinClassifierType(override val classifier: JavaClassifier) : JavaCl
}
private fun resolveSupertype(name: String,
classOrObject: KtClassOrObject,
javac: JavacWrapper): JavaClass? {
val nameParts = name.split(".")
val ktFile = classOrObject.containingKtFile
class MockKotlinField(private val property: KtProperty) : JavaField {
override val name: Name
get() = property.nameAsSafeName
override val annotations: Collection<JavaAnnotation>
get() = throw UnsupportedOperationException("Should not be called")
override val isDeprecatedInJavaDoc: Boolean
get() = throw UnsupportedOperationException("Should not be called")
override val isAbstract: Boolean
get() = throw UnsupportedOperationException("Should not be called")
override val isStatic: Boolean
get() = throw UnsupportedOperationException("Should not be called")
override val isFinal: Boolean
get() = throw UnsupportedOperationException("Should not be called")
override val visibility: Visibility
get() = throw UnsupportedOperationException("Should not be called")
override val containingClass: JavaClass
get() = throw UnsupportedOperationException("Should not be called")
override val isEnumEntry: Boolean
get() = throw UnsupportedOperationException("Should not be called")
override val type: JavaType
get() = throw UnsupportedOperationException("Should not be called")
override val initializerValue: Any?
get() {
if (!property.hasModifier(KtTokens.CONST_KEYWORD)) return null
val initializer = property.initializer ?: return null
tryToResolveInner(name, classOrObject, javac, nameParts)?.let { return it }
ktFile.tryToResolvePackageClass(name, javac, nameParts)?.let { return it }
tryToResolveByFqName(name, javac)?.let { return it }
ktFile.tryToResolveSingleTypeImport(name, javac, nameParts)?.let { return it }
ktFile.tryToResolveTypeImportOnDemand(name, javac, nameParts)?.let { return it }
tryToResolveInJavaLang(name, javac)?.let { return it }
return initializer.text.toIntOrNull() ?: initializer.text
}
override val hasConstantNotNullInitializer: Boolean
get() = throw UnsupportedOperationException("Should not be called")
return null
override fun findAnnotation(fqName: FqName) = throw UnsupportedOperationException("Should not be called")
}
private fun tryToResolveInner(name: String,
classOrObject: KtClassOrObject,
javac: JavacWrapper,
nameParts: List<String>) =
classOrObject.containingClassOrObject?.let { containingClass ->
containingClass.fqName?.let {
javac.findClass(it) ?: javac.getKotlinClassifier(it)
}
}?.findInner(name, javac, nameParts)
private fun KtFile.tryToResolvePackageClass(name: String,
javac: JavacWrapper,
nameParts: List<String> = emptyList()): JavaClass? {
return if (nameParts.size > 1) {
find(FqName("${packageFqName.asString()}.${nameParts.first()}"), javac, nameParts)
}
else {
javac.findClass(FqName("${packageFqName.asString()}.$name"))
?: javac.getKotlinClassifier(FqName("${packageFqName.asString()}.$name"))
}
}
private fun KtFile.tryToResolveSingleTypeImport(name: String,
javac: JavacWrapper,
nameParts: List<String> = emptyList()): JavaClass? {
if (nameParts.size > 1) {
val foundImports = importDirectives.filter { it.text.endsWith(".${nameParts.first()}") }
foundImports.forEach { importDirective ->
importDirective.importedFqName?.let { importedFqName ->
find(importedFqName, javac, nameParts)?.let { importedClass ->
return importedClass
}
}
}
return null
}
else {
return importDirectives.find { importDirective ->
importDirective.text.endsWith(".$name")
}?.let { importDirective ->
importDirective.importedFqName?.let { fqName ->
javac.findClass(fqName) ?: javac.getKotlinClassifier(fqName)
}
}
}
}
private fun KtFile.tryToResolveTypeImportOnDemand(name: String,
javac: JavacWrapper,
nameParts: List<String> = emptyList()): JavaClass? {
val packagesWithAsterisk = importDirectives.filter { it.text.endsWith("*") }
if (nameParts.size > 1) {
packagesWithAsterisk.forEach { importDirective ->
find(FqName("${importDirective.importedFqName?.asString()}.${nameParts.first()}"),
javac,
nameParts)?.let { return it }
}
return null
}
else {
packagesWithAsterisk.forEach { importDirective ->
val fqName = "${importDirective.importedFqName?.asString()}.$name".let(::FqName)
javac.findClass(fqName)?.let { return it } ?: javac.getKotlinClassifier(fqName)?.let { return it }
}
return null
}
}
private fun KtClassOrObject.computeClassId(): ClassId? =
containingClassOrObject?.computeClassId()?.createNestedClassId(nameAsSafeName) ?: fqName?.let { ClassId.topLevel(it) }
@@ -0,0 +1,24 @@
/*
* 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 org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtClassOrObject
interface KotlinSupertypeResolver {
fun resolveSupertypes(classOrObject: KtClassOrObject): List<ClassId>
}
@@ -31,6 +31,7 @@ import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.NoType
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import javax.tools.JavaFileObject
class SymbolBasedClass(
@@ -61,10 +62,11 @@ class SymbolBasedClass(
get() = FqName(element.qualifiedName.toString())
override val supertypes: Collection<JavaClassifierType>
get() = element.interfaces.toMutableList()
get() = arrayListOf<TypeMirror>()
.apply {
element.superclass.takeIf { it !is NoType }?.let(this::add)
}
.apply { addAll(element.interfaces) }
.mapTo(arrayListOf()) { SymbolBasedClassifierType(it, javac) }
.apply {
if (isEmpty() && element.qualifiedName.toString() != CommonClassNames.JAVA_LANG_OBJECT) {
@@ -97,9 +99,17 @@ class SymbolBasedClass(
override val methods: Collection<JavaMethod>
get() = element.enclosedElements
.filter { it.kind == ElementKind.METHOD }
.filter { it.kind == ElementKind.METHOD && !isEnumValuesOrValueOf(it as ExecutableElement)}
.map { SymbolBasedMethod(it as ExecutableElement, javac) }
private fun isEnumValuesOrValueOf(method: ExecutableElement): Boolean {
return isEnum && when (method.simpleName.toString()) {
"values" -> method.parameters.isEmpty()
"valueOf" -> method.parameters.let { it.size == 1 && it.first().asType().toString() == "java.lang.String" }
else -> false
}
}
override val fields: Collection<JavaField>
get() = element.enclosedElements
.filter { it.kind.isField && Name.isValidIdentifier(it.simpleName.toString()) }
@@ -32,7 +32,7 @@ sealed class SymbolBasedAnnotationArgument(
companion object {
fun create(value: Any, name: Name, javac: JavacWrapper): JavaAnnotationArgument = when (value) {
is AnnotationMirror -> SymbolBasedAnnotationAsAnnotationArgument(value, name, javac)
is VariableElement -> SymbolBasedReferenceAnnotationArgument(value, javac)
is VariableElement -> SymbolBasedReferenceAnnotationArgument(value, name, javac)
is TypeMirror -> SymbolBasedClassObjectAnnotationArgument(value, name, javac)
is Collection<*> -> arrayAnnotationArguments(value, name, javac)
is AnnotationValue -> create(value.value, name, javac)
@@ -59,10 +59,11 @@ class SymbolBasedAnnotationAsAnnotationArgument(
class SymbolBasedReferenceAnnotationArgument(
val element: VariableElement,
name: Name,
javac: JavacWrapper
) : SymbolBasedAnnotationArgument(Name.identifier(element.simpleName.toString()), javac), JavaEnumValueAnnotationArgument {
) : SymbolBasedAnnotationArgument(name, javac), JavaEnumValueAnnotationArgument {
override val entryName: Name?
get() = Name.identifier(element.simpleName.toString())
get() = Name.identifier(name)
override fun resolve() = SymbolBasedField(element, javac)
@@ -82,18 +82,29 @@ class SymbolBasedClassifierType<out T : TypeMirror>(
}
override val typeArguments: List<JavaType>
get() = if (typeMirror.kind == TypeKind.DECLARED) {
(typeMirror as DeclaredType).typeArguments.map { create(it, javac) }
}
else {
emptyList()
get() {
if (typeMirror.kind != TypeKind.DECLARED) return emptyList()
val arguments = arrayListOf<JavaType>()
var type = typeMirror as DeclaredType
var staticType = false
while (!staticType) {
if (type.asElement().isStatic) {
staticType = true
}
arguments.addAll(type.typeArguments.map { create(it, javac) })
type = type.enclosingType as? DeclaredType ?: return arguments
}
return arguments
}
override val isRaw: Boolean
get() = when {
typeMirror !is DeclaredType -> false
(typeMirror.asElement() as TypeElement).typeParameters.isEmpty() -> false
else -> typeMirror.typeArguments.isEmpty()
else -> typeMirror.typeArguments.isEmpty() || (typeMirror.asElement() as TypeElement).typeParameters.size != typeMirror.typeArguments.size
}
override val classifierQualifiedName: String
@@ -62,7 +62,7 @@ internal fun TypeElement.computeClassId(): ClassId? {
internal fun ExecutableElement.valueParameters(javac: JavacWrapper): List<JavaValueParameter> =
parameters.mapIndexed { index, parameter ->
SymbolBasedValueParameter(parameter,
parameter.simpleName.toString(),
if (!parameter.simpleName.contentEquals("arg$index")) parameter.simpleName.toString() else "p$index",
index == parameters.lastIndex && isVarArgs,
javac)
}
@@ -0,0 +1,348 @@
/*
* 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.wrappers.trees
import com.sun.source.tree.Tree
import com.sun.source.util.TreePath
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.javac.JavacWrapper
import org.jetbrains.kotlin.javac.MockKotlinClassifier
import org.jetbrains.kotlin.load.java.JavaVisibilities
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaClassifier
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
class ClassifierResolver(private val javac: JavacWrapper) {
private val cache = hashMapOf<Tree, JavaClassifier?>()
private val beingResolved = hashSetOf<Tree>()
fun resolve(treePath: TreePath): JavaClassifier? = with (treePath) {
if (cache.containsKey(leaf)) return cache[leaf]
if (treePath.leaf in beingResolved) return null
beingResolved(treePath.leaf)
return tryToResolve().apply {
cache[leaf] = this
removeBeingResolved(treePath.leaf)
}
}
// to avoid StackOverflow when there are cyclic dependencies
private fun beingResolved(tree: Tree) {
if (tree is JCTree.JCTypeApply) {
beingResolved(tree.clazz)
}
if (tree is JCTree.JCFieldAccess) {
beingResolved.add(tree)
beingResolved(tree.selected)
}
else beingResolved.add(tree)
}
private fun removeBeingResolved(tree: Tree) {
if (tree is JCTree.JCTypeApply) {
beingResolved(tree.clazz)
}
if (tree is JCTree.JCFieldAccess) {
beingResolved.remove(tree)
beingResolved(tree.selected)
}
else beingResolved.remove(tree)
}
private fun pathSegments(path: String): List<String> {
val pathSegments = arrayListOf<String>()
var numberOfBrackets = 0
var builder = StringBuilder()
path.forEach { char ->
when (char) {
'<' -> numberOfBrackets++
'>' -> numberOfBrackets--
'.' -> {
if (numberOfBrackets == 0) {
pathSegments.add(builder.toString())
builder = StringBuilder()
}
}
'@' -> {}
else -> if (numberOfBrackets == 0) builder.append(char)
}
}
return pathSegments.apply { add(builder.toString()) }
}
private fun TreePath.tryToResolve(): JavaClassifier? {
val pathSegments = pathSegments(leaf.toString())
return tryToGetTypeParameterFromMethod()?.let { return it } ?:
createResolutionScope(this).findClass(pathSegments.first(), pathSegments)
}
private fun TreePath.tryToGetTypeParameterFromMethod(): TreeBasedTypeParameter? =
(find { it is JCTree.JCMethodDecl } as? JCTree.JCMethodDecl)
?.typarams?.find { it.name.toString() == leaf.toString() }
?.let {
TreeBasedTypeParameter(it,
javac.getTreePath(it, compilationUnit),
javac)
}
private fun createResolutionScope(treePath: TreePath): Scope = CurrentClassAndInnerScope(javac, treePath)
}
private abstract class Scope(protected val javac: JavacWrapper,
protected val treePath: TreePath) {
abstract val parent: Scope?
abstract fun findClass(name: String, pathSegments: List<String>): JavaClassifier?
protected fun getJavaClassFromPathSegments(javaClass: JavaClass,
pathSegments: List<String>) =
if (pathSegments.size == 1) {
javaClass
}
else {
javaClass.findInnerOrNested(pathSegments.drop(1))
}
protected fun findImport(pathSegments: List<String>): JavaClass? {
pathSegments.forEachIndexed { index, _ ->
if (index == pathSegments.lastIndex) return null
val packageFqName = pathSegments.dropLast(index + 1).joinToString(separator = ".")
findPackage(packageFqName)?.let { pack ->
val className = pathSegments.takeLast(index + 1)
return findJavaOrKotlinClass(ClassId(pack, Name.identifier(className.first())))?.let { javaClass ->
getJavaClassFromPathSegments(javaClass, className)
}
}
}
return null
}
protected fun findJavaOrKotlinClass(classId: ClassId) = javac.findClass(classId) ?: javac.getKotlinClassifier(classId)
protected fun JavaClass.findInnerOrNested(name: Name, checkedSupertypes: HashSet<JavaClass> = hashSetOf()): JavaClass? {
findVisibleInnerOrNestedClass(name)?.let {
checkedSupertypes.addAll(collectAllSupertypes())
return it
}
return supertypes
.mapNotNull {
(it.classifier as? JavaClass)?.let { supertype ->
if (supertype !in checkedSupertypes) {
supertype.findInnerOrNested(name, checkedSupertypes)
} else null
}
}.singleOrNull()
}
protected fun findPackage(packageName: String): FqName? {
val fqName = if (packageName.isNotBlank()) FqName(packageName) else FqName.ROOT
javac.hasKotlinPackage(fqName)?.let { return it }
return javac.findPackage(fqName)?.fqName
}
private fun JavaClass.findVisibleInnerOrNestedClass(name: Name) = findInnerClass(name)?.let { innerOrNestedClass ->
when (innerOrNestedClass.visibility) {
Visibilities.PRIVATE -> null
JavaVisibilities.PACKAGE_VISIBILITY -> {
val classId = (innerOrNestedClass as? MockKotlinClassifier)?.classId ?: innerOrNestedClass.computeClassId()
if (classId?.packageFqName?.asString() == (treePath.compilationUnit.packageName?.toString() ?: "")) innerOrNestedClass else null
}
else -> innerOrNestedClass
}
}
private fun JavaClass.collectAllSupertypes(): Set<JavaClass> =
hashSetOf(this).apply {
supertypes.mapNotNull { it.classifier as? JavaClass }.forEach { addAll(it.collectAllSupertypes()) }
}
private fun JavaClass.findInnerOrNested(pathSegments: List<String>): JavaClass? =
pathSegments.fold(this) { javaClass, it -> javaClass.findInnerOrNested(Name.identifier(it)) ?: return null }
}
private class GlobalScope(javac: JavacWrapper, treePath: TreePath) : Scope(javac, treePath) {
override val parent: Scope?
get() = null
override fun findClass(name: String, pathSegments: List<String>): JavaClass? {
findByFqName(pathSegments)?.let { return it }
return findJavaOrKotlinClass(classId("java.lang", name))?.let { javaClass ->
getJavaClassFromPathSegments(javaClass, pathSegments)
}
}
private fun findByFqName(pathSegments: List<String>): JavaClass? {
pathSegments.forEachIndexed { index, _ ->
if (index != 0) {
val packageFqName = pathSegments.take(index).joinToString(separator = ".")
findPackage(packageFqName)?.let { pack ->
val className = pathSegments.drop(index)
findJavaOrKotlinClass(ClassId(pack, Name.identifier(className.first())))?.let { javaClass ->
return getJavaClassFromPathSegments(javaClass, className)
}
}
}
}
// try to find in <root>
return findJavaOrKotlinClass(classId("", pathSegments.first()))?.let { javaClass ->
getJavaClassFromPathSegments(javaClass, pathSegments)
}
}
}
private class ImportOnDemandScope(javac: JavacWrapper,
treePath: TreePath) : Scope(javac, treePath) {
override val parent: Scope
get() = GlobalScope(javac, treePath)
override fun findClass(name: String, pathSegments: List<String>): JavaClassifier? {
asteriskImports()
.mapNotNullTo(hashSetOf()) { findImport("$it$name".split(".")) }
.takeIf { it.isNotEmpty() }
?.let {
return it.singleOrNull()?.let { javaClass ->
getJavaClassFromPathSegments(javaClass, pathSegments)
}
}
return parent.findClass(name, pathSegments)
}
private fun asteriskImports() =
treePath.compilationUnit.imports
.mapNotNull {
val fqName = it.qualifiedIdentifier.toString()
if (fqName.endsWith("*")) {
fqName.dropLast(1)
}
else null
}
}
private class PackageScope(javac: JavacWrapper,
treePath: TreePath) : Scope(javac, treePath) {
override val parent: Scope
get() = ImportOnDemandScope(javac, treePath)
override fun findClass(name: String, pathSegments: List<String>): JavaClassifier? {
findJavaOrKotlinClass(classId(treePath.compilationUnit.packageName?.toString() ?: "", name))
?.let { javaClass ->
return getJavaClassFromPathSegments(javaClass, pathSegments)
}
return parent.findClass(name, pathSegments)
}
}
private class SingleTypeImportScope(javac: JavacWrapper,
treePath: TreePath) : Scope(javac, treePath) {
override val parent: Scope
get() = PackageScope(javac, treePath)
override fun findClass(name: String, pathSegments: List<String>): JavaClassifier? {
val imports = imports(name).toSet().takeIf { it.isNotEmpty() }
?: return parent.findClass(name, pathSegments)
imports.singleOrNull() ?: return null
return findImport(imports.first().split("."))
?.let { javaClass -> getJavaClassFromPathSegments(javaClass, pathSegments) }
}
private fun imports(firstSegment: String) =
(treePath.compilationUnit as JCTree.JCCompilationUnit).imports
.mapNotNull {
val fqName = it.qualifiedIdentifier.toString()
if (fqName.endsWith(".$firstSegment")) {
fqName
}
else null
}
}
private class CurrentClassAndInnerScope(javac: JavacWrapper,
treePath: TreePath) : Scope(javac, treePath) {
override val parent: Scope
get() = SingleTypeImportScope(javac, treePath)
override fun findClass(name: String, pathSegments: List<String>): JavaClassifier? {
val identifier = Name.identifier(name)
treePath.enclosingClasses.forEach {
(it as? TreeBasedClass)?.typeParameters
?.find { typeParameter -> typeParameter.name == identifier }
?.let { typeParameter -> return typeParameter }
it.findInnerOrNested(identifier)?.let { javaClass -> return getJavaClassFromPathSegments(javaClass, pathSegments) }
if (it.name == identifier && pathSegments.size == 1) return it
}
return parent.findClass(name, pathSegments)
}
private val TreePath.enclosingClasses: List<JavaClass>
get() {
val outerClasses = filterIsInstance<JCTree.JCClassDecl>()
.dropWhile { it.extending == leaf || leaf in it.implementing }
.asReversed()
.map { it.simpleName.toString() }
val packageName = compilationUnit.packageName?.toString() ?: ""
val outermostClassName = outerClasses.firstOrNull() ?: return emptyList()
val outermostClassId = classId(packageName, outermostClassName)
var outermostClass = javac.findClass(outermostClassId) ?: return emptyList()
val classes = arrayListOf<JavaClass>()
classes.add(outermostClass)
for (it in outerClasses.drop(1)) {
outermostClass = outermostClass.findInnerClass(Name.identifier(it))
?: throw AssertionError("Couldn't find a class ($it) that is surely defined in ${outermostClass.fqName?.asString()}")
classes.add(outermostClass)
}
return classes.reversed()
}
}
fun classId(packageName: String = "", className: String) = ClassId(FqName(packageName), Name.identifier(className))
@@ -16,38 +16,137 @@
package org.jetbrains.kotlin.javac.wrappers.trees
import com.sun.source.tree.CompilationUnitTree
import com.sun.source.util.TreePath
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.javac.JavacWrapper
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaAnnotationArgument
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaElement
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
class TreeBasedAnnotation(
private val annotation: JCTree.JCAnnotation,
private val compilationUnit: CompilationUnitTree,
private val javac: JavacWrapper
val annotation: JCTree.JCAnnotation,
val treePath: TreePath,
val javac: JavacWrapper
) : JavaElement, JavaAnnotation {
constructor(
annotation: JCTree.JCAnnotation,
treePath: TreePath,
javac: JavacWrapper
) : this(annotation, treePath.compilationUnit, javac)
override val arguments: Collection<JavaAnnotationArgument>
get() = annotation.arguments.map { TreeBasedAnnotationArgument(Name.identifier(it.toString())) }
get() = createAnnotationArguments(this, javac)
override val classId: ClassId?
get() = resolve()?.computeClassId()
get() = resolve()?.computeClassId() ?: ClassId.topLevel(FqName(annotation.annotationType.toString().substringAfter("@")))
override fun resolve() =
javac.resolve(TreePath.getPath(compilationUnit, annotation.annotationType)) as? JavaClass
javac.resolve(TreePath.getPath(treePath.compilationUnit, annotation.annotationType)) as? JavaClass
}
class TreeBasedAnnotationArgument(override val name: Name) : JavaAnnotationArgument, JavaElement
sealed class TreeBasedAnnotationArgument(override val name: Name,
val javac: JavacWrapper) : JavaAnnotationArgument, JavaElement
class TreeBasedLiteralAnnotationArgument(name: Name,
override val value: Any?,
javac: JavacWrapper) : TreeBasedAnnotationArgument(name, javac), JavaLiteralAnnotationArgument
class TreeBasedReferenceAnnotationArgument(name: Name,
private val treePath: TreePath,
private val field: JCTree.JCFieldAccess,
javac: JavacWrapper) : TreeBasedAnnotationArgument(name, javac), JavaEnumValueAnnotationArgument {
override fun resolve(): JavaField? {
val newTreePath = javac.getTreePath(field.selected, treePath.compilationUnit)
val javaClass = javac.resolve(newTreePath) as? JavaClass ?: return null
val fieldName = field.name.toString().let { Name.identifier(it) }
return javaClass.fields.find { it.name == fieldName }
}
}
class TreeBasedArrayAnnotationArgument(val args: List<JavaAnnotationArgument>,
name: Name,
javac: JavacWrapper): TreeBasedAnnotationArgument(name, javac), JavaArrayAnnotationArgument {
override fun getElements() = args
}
class TreeBasedJavaClassObjectAnnotationArgument(private val type: JCTree.JCExpression,
name: Name,
private val treePath: TreePath,
javac: JavacWrapper): TreeBasedAnnotationArgument(name, javac), JavaClassObjectAnnotationArgument {
override fun getReferencedType(): JavaType =
TreeBasedType.create(type, javac.getTreePath(type, treePath.compilationUnit), javac, emptyList())
}
class TreeBasedAnnotationAsAnnotationArgument(private val annotation: JCTree.JCAnnotation,
name: Name,
private val treePath: TreePath,
javac: JavacWrapper): TreeBasedAnnotationArgument(name, javac), JavaAnnotationAsAnnotationArgument {
override fun getAnnotation(): JavaAnnotation =
TreeBasedAnnotation(annotation, javac.getTreePath(annotation, treePath.compilationUnit), javac )
}
private fun createAnnotationArguments(annotation: TreeBasedAnnotation,
javac: JavacWrapper): Collection<JavaAnnotationArgument> {
val arguments = annotation.annotation.arguments
val javaClass = annotation.resolve() ?: return emptyList()
val methods = javaClass.methods
if (arguments.size != methods.size) return emptyList()
return methods.mapIndexedNotNull { index, it ->
createAnnotationArgument(arguments[index], it.name, annotation.treePath, javac, annotation)
}
}
private fun createAnnotationArgument(argument: JCTree.JCExpression,
name: Name,
treePath: TreePath,
javac: JavacWrapper,
annotation: TreeBasedAnnotation): JavaAnnotationArgument? =
when (argument) {
is JCTree.JCLiteral -> TreeBasedLiteralAnnotationArgument(name, argument.value, javac)
is JCTree.JCFieldAccess -> {
if (argument.name.contentEquals("class")) {
TreeBasedJavaClassObjectAnnotationArgument(argument.selected, name, treePath, javac)
} else {
TreeBasedReferenceAnnotationArgument(name, treePath, argument, javac)
}
}
is JCTree.JCAssign -> createAnnotationArgument(argument.rhs, name, treePath, javac, annotation)
is JCTree.JCNewArray -> arrayAnnotationArguments(argument.elems, name, treePath, javac, annotation)
is JCTree.JCAnnotation -> TreeBasedAnnotationAsAnnotationArgument(argument, name, treePath, javac)
is JCTree.JCParens -> createAnnotationArgument(argument.expr, name, treePath, javac, annotation)
is JCTree.JCBinary -> resolveArgumentValue(argument, annotation, name, treePath, javac)
is JCTree.JCUnary -> resolveArgumentValue(argument, annotation, name, treePath, javac)
else -> null
}
private fun resolveArgumentValue(argument: JCTree.JCExpression,
annotation: TreeBasedAnnotation,
name: Name,
treePath: TreePath,
javac: JavacWrapper): JavaAnnotationArgument? {
val containingAnnotation = annotation.resolve()
val type = containingAnnotation?.methods?.find { it.name == name }?.returnType ?: return null
val calculator = ValueCalculator(containingAnnotation, javac, treePath, type)
return calculator.getValue(argument)?.let { TreeBasedLiteralAnnotationArgument(name, it, javac) }
}
private fun arrayAnnotationArguments(values: List<JCTree.JCExpression>,
name: Name,
treePath: TreePath,
javac: JavacWrapper,
annotation: TreeBasedAnnotation): JavaArrayAnnotationArgument =
values.mapNotNull {
if (it is JCTree.JCNewArray) {
arrayAnnotationArguments(it.elems, name, treePath, javac, annotation)
}
else {
createAnnotationArgument(it, name, treePath, javac, annotation)
}
}.let { TreeBasedArrayAnnotationArgument(it, name, javac) }
@@ -50,54 +50,52 @@ class TreeBasedClass(
annotations.find { it.classId?.asSingleFqName() == fqName }
override val isDeprecatedInJavaDoc: Boolean
get() = false
get() = javac.isDeprecatedInJavaDoc(treePath)
override val isAbstract: Boolean
get() = tree.modifiers.isAbstract || (isAnnotationType && methods.any { it.isAbstract })
get() = tree.modifiers.isAbstract || ((isAnnotationType || isEnum) && methods.any { it.isAbstract })
override val isStatic: Boolean
get() = (outerClass?.isInterface ?: false) || tree.modifiers.isStatic
get() = isEnum || isInterface || (outerClass?.isInterface ?: false) || tree.modifiers.isStatic
override val isFinal: Boolean
get() = tree.modifiers.isFinal
get() = isEnum || tree.modifiers.isFinal
override val visibility: Visibility
get() = if (outerClass?.isInterface ?: false) PUBLIC else tree.modifiers.visibility
get() = if (outerClass?.isInterface == true) PUBLIC else tree.modifiers.visibility
override val typeParameters: List<JavaTypeParameter>
get() = tree.typeParameters.map { parameter ->
TreeBasedTypeParameter(parameter, TreePath(treePath, parameter), javac)
}
override val fqName: FqName =
treePath.reversed()
.filterIsInstance<JCTree.JCClassDecl>()
.joinToString(
separator = ".",
prefix = "${treePath.compilationUnit.packageName}.",
transform = JCTree.JCClassDecl::name
)
.let(::FqName)
override val fqName: FqName
get() = treePath.reversed()
.filterIsInstance<JCTree.JCClassDecl>()
.joinToString(
separator = ".",
transform = JCTree.JCClassDecl::name
)
.let { treePath.compilationUnit.packageName?.let { packageName -> FqName("$packageName.$it") } ?: FqName.topLevel(Name.identifier(it))}
override val supertypes: Collection<JavaClassifierType>
get() = arrayListOf<JavaClassifierType>().apply {
fun JCTree.mapToJavaClassifierType() = when {
this is JCTree.JCTypeApply -> TreeBasedGenericClassifierType(this, TreePath(treePath, this), javac)
this is JCTree.JCExpression -> TreeBasedNonGenericClassifierType(this, TreePath(treePath, this), javac)
else -> null
}
get() = arrayListOf<JavaClassifierType>().also { list ->
if (isEnum) {
javac.JAVA_LANG_ENUM?.let(this::add)
createEnumSupertype(this, javac).let { list.add(it) }
} else if (isAnnotationType) {
javac.JAVA_LANG_ANNOTATION_ANNOTATION?.let(this::add)
javac.JAVA_LANG_ANNOTATION_ANNOTATION?.let { list.add(it) }
}
tree.implementing?.mapNotNull { it.mapToJavaClassifierType() }?.let(this::addAll)
tree.extending?.let { it.mapToJavaClassifierType()?.let(this::add) }
tree.extending?.let {
(TreeBasedType.create(it, javac.getTreePath(it, treePath.compilationUnit), javac, emptyList()) as? JavaClassifierType)
?.let { list.add(it) }
}
tree.implementing?.mapNotNull {
TreeBasedType.create(it, javac.getTreePath(it, treePath.compilationUnit), javac, emptyList()) as? JavaClassifierType
}?.let { list.addAll(it) }
if (isEmpty()) {
javac.JAVA_LANG_OBJECT?.let(this::add)
if (list.isEmpty()) {
javac.JAVA_LANG_OBJECT?.let { list.add(it) }
}
}
@@ -155,3 +153,44 @@ class TreeBasedClass(
override fun findInnerClass(name: Name) = innerClasses[name]
}
private fun createEnumSupertype(javaClass: JavaClass,
javac: JavacWrapper) = object : JavaClassifierType {
override val classifier: JavaClassifier?
get() = javac.JAVA_LANG_ENUM
override val typeArguments: List<JavaType>
get() = listOf(TypeArgument())
override val isRaw: Boolean
get() = false
override val annotations: Collection<JavaAnnotation>
get() = emptyList()
override val classifierQualifiedName: String
get() = (classifier as? JavaClass)?.fqName?.asString() ?: ""
override val presentableText: String
get() = classifierQualifiedName
override val isDeprecatedInJavaDoc: Boolean
get() = false
override fun findAnnotation(fqName: FqName) = null
private inner class TypeArgument : JavaClassifierType {
override val classifier: JavaClassifier?
get() = javaClass
override val typeArguments: List<JavaType>
get() = emptyList()
override val isRaw: Boolean
get() = false
override val annotations: Collection<JavaAnnotation>
get() = emptyList()
override val classifierQualifiedName: String
get() = javaClass.fqName!!.asString()
override val presentableText: String
get() = classifierQualifiedName
override val isDeprecatedInJavaDoc: Boolean
get() = false
override fun findAnnotation(fqName: FqName) = null
}
}
@@ -18,12 +18,15 @@ package org.jetbrains.kotlin.javac.wrappers.trees
import com.sun.source.util.TreePath
import com.sun.tools.javac.code.Flags
import com.sun.tools.javac.code.TypeTag
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.javac.JavacWrapper
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaField
import org.jetbrains.kotlin.load.java.structure.JavaPrimitiveType
import org.jetbrains.kotlin.load.java.structure.JavaType
import org.jetbrains.kotlin.name.Name
@@ -53,19 +56,16 @@ class TreeBasedField(
get() = tree.modifiers.flags and Flags.ENUM.toLong() != 0L
override val type: JavaType
get() = TreeBasedType.create(tree.getType(), treePath, javac)
get() = TreeBasedType.create(tree.getType(), treePath, javac, annotations)
override val initializerValue: Any?
get() = tree.init?.let { initExpr ->
if (hasConstantNotNullInitializer && initExpr is JCTree.JCLiteral) {
initExpr.value
} else {
null
}
if (hasConstantNotNullInitializer) ValueCalculator(containingClass, javac, treePath, type).getValue(initExpr) else null
}
override val hasConstantNotNullInitializer: Boolean
get() = tree.init?.let {
if (it is JCTree.JCLiteral && it.value == null) return false
val type = this.type
isFinal && ((type is TreeBasedPrimitiveType) ||
@@ -73,4 +73,104 @@ class TreeBasedField(
type.classifierQualifiedName == "java.lang.String"))
} ?: false
}
class ValueCalculator(private val containingClass: JavaClass,
private val javac: JavacWrapper,
private val treePath: TreePath,
private val type: JavaType) {
fun getValue(expr: JCTree.JCExpression): Any? {
return when (expr) {
is JCTree.JCLiteral -> {
if (expr.typetag == TypeTag.BOOLEAN) {
expr.value != 0
}
else expr.value
}
is JCTree.JCIdent -> containingClass.fields
.find { it.name == expr.name.toString().let { Name.identifier(it) } }
?.initializerValue
is JCTree.JCFieldAccess -> fieldAccessValue(expr)
is JCTree.JCBinary -> binaryInitializerValue(expr)
is JCTree.JCParens -> getValue(expr.expr)
is JCTree.JCUnary -> unaryInitializerValue(expr)
else -> null
}
}
private fun fieldAccessValue(value: JCTree.JCFieldAccess): Any? {
val newTreePath = javac.getTreePath(value.selected, treePath.compilationUnit)
val javaClass = javac.resolve(newTreePath) as? JavaClass ?: return null
val fieldName = value.name.toString().let { Name.identifier(it) }
return javaClass.fields
.find { it.name == fieldName }
?.initializerValue
}
private fun unaryInitializerValue(value: JCTree.JCUnary): Any? {
val argValue = getValue(value.arg)
return when (value.tag) {
JCTree.Tag.COMPL -> (argValue as? Int)?.inv()
JCTree.Tag.NOT -> (argValue as? Boolean)?.let { !it }
else -> null
}
}
private fun binaryInitializerValue(value: JCTree.JCBinary): Any? {
val lhsValue = getValue(value.lhs) ?: return null
val rhsValue = getValue(value.rhs) ?: return null
return calculateValue(lhsValue, rhsValue, value.tag)
}
private fun calculateValue(lhsValue: Any?, rhsValue: Any?, opcode: JCTree.Tag): Any? {
if (lhsValue is String && opcode == JCTree.Tag.PLUS) return lhsValue + rhsValue
if (lhsValue is Boolean && rhsValue is Boolean) {
return when (opcode) {
JCTree.Tag.AND -> lhsValue && rhsValue
JCTree.Tag.OR -> lhsValue || rhsValue
JCTree.Tag.EQ -> lhsValue == rhsValue
JCTree.Tag.NE -> lhsValue != rhsValue
else -> null
}
}
val l = (lhsValue as? Number)?.toInt() ?: return null
val r = (rhsValue as? Number)?.toInt() ?: return null
return when (opcode) {
JCTree.Tag.PLUS -> getExpressionType(l + r)
JCTree.Tag.MINUS -> getExpressionType(l - r)
JCTree.Tag.MUL -> getExpressionType(l * r)
JCTree.Tag.DIV -> getExpressionType(l / r)
JCTree.Tag.MOD -> getExpressionType(l % r)
JCTree.Tag.SR -> getExpressionType(l shr r)
JCTree.Tag.SL -> getExpressionType(l shl r)
JCTree.Tag.BITAND -> getExpressionType(l and r)
JCTree.Tag.BITOR -> getExpressionType(l or r)
JCTree.Tag.BITXOR -> getExpressionType(l xor r)
JCTree.Tag.USR -> getExpressionType(l ushr r)
JCTree.Tag.EQ -> l == r
JCTree.Tag.NE -> l != r
JCTree.Tag.LT -> l < r
JCTree.Tag.LE -> l <= r
JCTree.Tag.GT -> l > r
JCTree.Tag.GE -> l >= r
else -> null
}
}
private fun getExpressionType(expression: Int): Any? {
val type = type as? JavaPrimitiveType ?: return null
return when (type.type) {
PrimitiveType.DOUBLE -> expression.toDouble()
PrimitiveType.INT -> expression
PrimitiveType.FLOAT -> expression.toFloat()
PrimitiveType.LONG -> expression.toLong()
PrimitiveType.SHORT -> expression.toShort()
PrimitiveType.BYTE -> expression.toByte()
else -> null
}
}
}
@@ -32,7 +32,7 @@ abstract class TreeBasedMember<out T : JCTree>(
) : TreeBasedElement<T>(tree, treePath, javac), JavaMember {
override val isDeprecatedInJavaDoc: Boolean
get() = false
get() = javac.isDeprecatedInJavaDoc(treePath)
override val annotations: Collection<JavaAnnotation> by lazy {
tree.annotations().map { TreeBasedAnnotation(it, treePath, javac) }
@@ -53,7 +53,7 @@ class TreeBasedMethod(
get() = tree.parameters.map { TreeBasedValueParameter(it, TreePath(treePath, it), javac) }
override val returnType: JavaType
get() = TreeBasedType.create(tree.returnType, treePath, javac)
get() = TreeBasedType.create(tree.returnType, treePath, javac, annotations)
override val hasAnnotationParameterDefaultValue: Boolean
get() = tree.defaultValue != null
@@ -42,15 +42,22 @@ class TreeBasedTypeParameter(
annotations.firstOrNull { it.classId?.asSingleFqName() == fqName }
override val isDeprecatedInJavaDoc: Boolean
get() = false
get() = javac.isDeprecatedInJavaDoc(treePath)
override val upperBounds: Collection<JavaClassifierType>
get() = tree.bounds.mapNotNull {
when (it) {
is JCTree.JCTypeApply -> TreeBasedGenericClassifierType(it, TreePath(treePath, it), javac)
is JCTree.JCIdent -> TreeBasedNonGenericClassifierType(it, TreePath(treePath, it), javac)
else -> null
}
TreeBasedType.create(it, TreePath(treePath, it), javac, emptyList()) as? JavaClassifierType
}
override fun equals(other: Any?): Boolean {
if (other !is TreeBasedTypeParameter) return false
return other.name == name && other.upperBounds == upperBounds
}
override fun hashCode(): Int {
var result = name.hashCode()
upperBounds.forEach { result = 37 * result + it.hashCode() }
return result
}
}
@@ -40,13 +40,13 @@ class TreeBasedValueParameter(
annotations.find { it.classId?.asSingleFqName() == fqName }
override val isDeprecatedInJavaDoc: Boolean
get() = false
get() = javac.isDeprecatedInJavaDoc(treePath)
override val name: Name
get() = Name.identifier(tree.name.toString())
override val type: JavaType
get() = TreeBasedType.create(tree.getType(), treePath, javac)
get() = TreeBasedType.create(tree.getType(), treePath, javac, annotations)
override val isVararg: Boolean
get() = tree.modifiers.flags and Flags.VARARGS != 0L
@@ -1,193 +0,0 @@
/*
* 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.wrappers.trees
import com.sun.source.tree.Tree
import com.sun.source.util.TreePath
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.javac.JavacWrapper
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaClassifier
import org.jetbrains.kotlin.name.FqName
class TreePathResolverCache(private val javac: JavacWrapper) {
private val cache = hashMapOf<Tree, JavaClassifier?>()
fun resolve(treePath: TreePath): JavaClassifier? = with(treePath) {
if (cache.containsKey(leaf)) return cache[leaf]
return tryToGetClassifier().apply { cache[leaf] = this }
}
private fun TreePath.tryToGetClassifier(): JavaClassifier? {
val name = leaf.toString().substringBefore("<").substringAfter("@")
val nameParts = name.split(".")
with(compilationUnit as JCTree.JCCompilationUnit) {
tryToResolveInner(name, javac, nameParts)?.let { return it }
tryToResolvePackageClass(name, javac, nameParts)?.let { return it }
tryToResolveByFqName(name, javac)?.let { return it }
tryToResolveSingleTypeImport(name, javac, nameParts)?.let { return it }
tryToResolveTypeImportOnDemand(name, javac, nameParts)?.let { return it }
tryToResolveInJavaLang(name, javac)?.let { return it }
}
return tryToResolveTypeParameter(javac)
}
private fun TreePath.tryToResolveInner(
name: String,
javac: JavacWrapper,
nameParts: List<String> = emptyList()
): JavaClass? = findEnclosingClasses(javac)?.forEach { javaClass ->
javaClass.findInner(name, javac, nameParts)?.let { inner -> return inner }
}.let { return null }
private fun TreePath.findEnclosingClasses(javac: JavacWrapper) =
filterIsInstance<JCTree.JCClassDecl>()
.filter { it.extending != leaf && !it.implementing.contains(leaf) }
.reversed()
.joinToString(separator = ".", prefix = "${compilationUnit.packageName}.") { it.simpleName }
.let { javac.findClass(FqName(it)) }
?.let {
arrayListOf(it).apply {
var enclosingClass = it.outerClass
while (enclosingClass != null) {
add(enclosingClass)
enclosingClass = enclosingClass.outerClass
}
}
}
private fun JCTree.JCCompilationUnit.tryToResolveSingleTypeImport(
name: String,
javac: JavacWrapper,
nameParts: List<String> = emptyList()
): JavaClass? {
nameParts.size.takeIf { it > 1 }?.let {
imports.filter { it.qualifiedIdentifier.toString().endsWith(".${nameParts.first()}") }
.forEach { import ->
find(FqName("${import.qualifiedIdentifier}"), javac, nameParts)?.let { javaClass ->
return javaClass
}
}
.let { return null }
}
return imports
.find { it.qualifiedIdentifier.toString().endsWith(".$name") }
?.let { import ->
FqName(import.qualifiedIdentifier.toString()).let { fqName ->
javac.findClass(fqName) ?: javac.getKotlinClassifier(fqName)
}
}
}
private fun JCTree.JCCompilationUnit.tryToResolvePackageClass(
name: String,
javac: JavacWrapper,
nameParts: List<String> = emptyList()
): JavaClass? {
return nameParts.size.takeIf { it > 1 }?.let {
find(FqName("$packageName.${nameParts.first()}"), javac, nameParts)
}
?: javac.findClass(FqName("$packageName.$name"))
?: javac.getKotlinClassifier(FqName("$packageName.$name"))
}
private fun JCTree.JCCompilationUnit.tryToResolveTypeImportOnDemand(
name: String,
javac: JavacWrapper,
nameParts: List<String> = emptyList()
): JavaClass? {
with(imports.filter { it.qualifiedIdentifier.toString().endsWith("*") }) {
nameParts.size.takeIf { it > 1 }
?.let {
forEach { pack ->
find(FqName("${pack.qualifiedIdentifier.toString().substringBefore("*")}${nameParts.first()}"),
javac,
nameParts)?.let { return it }
}.let { return null }
}
this.forEach {
val fqName = "${it.qualifiedIdentifier.toString().substringBefore("*")}$name".let(::FqName)
(javac.findClass(fqName) ?: javac.getKotlinClassifier(fqName))?.let { return it }
}.let { return null }
}
}
private fun TreePath.tryToResolveTypeParameter(javac: JavacWrapper) =
flatMap {
when (it) {
is JCTree.JCClassDecl -> it.typarams
is JCTree.JCMethodDecl -> it.typarams
else -> emptyList<JCTree.JCTypeParameter>()
}
}
.find { it.toString().substringBefore(" ") == leaf.toString() }
?.let {
TreeBasedTypeParameter(it,
javac.getTreePath(it, compilationUnit),
javac)
}
}
fun JavaClass.findInner(name: String,
javac: JavacWrapper,
nameParts: List<String> = emptyList()) : JavaClass? {
nameParts.size.takeIf { it > 1 }?.let {
return find(FqName("${fqName!!.asString()}.${nameParts[0]}"), javac, nameParts)
}
if (name == this.fqName?.shortName()?.asString()) return this
with(FqName("${fqName!!.asString()}.$name")) {
javac.findClass(this)?.let { return it }
javac.getKotlinClassifier(this)?.let { return it }
}
supertypes.mapNotNull { it.classifier as? JavaClass }
.forEach { javaClass ->
javaClass.findInner(name, javac)?.let { inner -> return inner }
}.let { return null }
}
fun tryToResolveByFqName(name: String,
javac: JavacWrapper) = with(FqName(name)) {
javac.findClass(this) ?: javac.getKotlinClassifier(this)
}
fun tryToResolveInJavaLang(name: String,
javac: JavacWrapper) = javac.findClass(FqName("java.lang.$name"))
fun find(fqName: FqName,
javac: JavacWrapper,
nameParts: List<String>): JavaClass? {
val initial = with(fqName) {
javac.findClass(this)
?: javac.getKotlinClassifier(this)
?: return null
}
nameParts.drop(1).fold(initial) {
javaClass, namePart -> javaClass.findInner(namePart, javac) ?: return null
}.let { return it }
}
@@ -30,23 +30,31 @@ import javax.lang.model.type.TypeKind
abstract class TreeBasedType<out T : JCTree>(
val tree: T,
val treePath: TreePath,
val javac: JavacWrapper
val javac: JavacWrapper,
override val annotations: Collection<JavaAnnotation>
) : JavaType, JavaAnnotationOwner {
companion object {
fun <Type : JCTree> create(tree: Type, treePath: TreePath, javac: JavacWrapper) = when (tree) {
is JCTree.JCPrimitiveTypeTree -> TreeBasedPrimitiveType(tree, TreePath(treePath, tree), javac)
is JCTree.JCArrayTypeTree -> TreeBasedArrayType(tree, TreePath(treePath, tree), javac)
is JCTree.JCWildcard -> TreeBasedWildcardType(tree, TreePath(treePath, tree), javac)
is JCTree.JCTypeApply -> TreeBasedGenericClassifierType(tree, TreePath(treePath, tree), javac)
is JCTree.JCExpression -> TreeBasedNonGenericClassifierType(tree, TreePath(treePath, tree), javac)
else -> throw UnsupportedOperationException("Unsupported type: $tree")
fun create(tree: JCTree, treePath: TreePath,
javac: JavacWrapper, annotations: Collection<JavaAnnotation>): JavaType {
val applicableAnnotations = annotations.filterTypeAnnotations()
return when (tree) {
is JCTree.JCPrimitiveTypeTree -> TreeBasedPrimitiveType(tree, javac.getTreePath(tree, treePath.compilationUnit), javac, applicableAnnotations)
is JCTree.JCArrayTypeTree -> TreeBasedArrayType(tree, javac.getTreePath(tree, treePath.compilationUnit), javac, applicableAnnotations)
is JCTree.JCWildcard -> TreeBasedWildcardType(tree, javac.getTreePath(tree, treePath.compilationUnit), javac, applicableAnnotations)
is JCTree.JCTypeApply -> TreeBasedGenericClassifierType(tree, javac.getTreePath(tree, treePath.compilationUnit), javac, applicableAnnotations)
is JCTree.JCAnnotatedType -> {
val underlyingType = tree.underlyingType
val newAnnotations = tree.annotations
.map { TreeBasedAnnotation(it, javac.getTreePath(it, treePath.compilationUnit), javac) }
create(underlyingType, javac.getTreePath(underlyingType, treePath.compilationUnit), javac, newAnnotations)
}
is JCTree.JCExpression -> TreeBasedNonGenericClassifierType(tree, javac.getTreePath(tree, treePath.compilationUnit), javac, applicableAnnotations)
else -> throw UnsupportedOperationException("Unsupported type: $tree")
}
}
}
override val annotations: Collection<JavaAnnotation>
get() = emptyList()
override val isDeprecatedInJavaDoc: Boolean
get() = false
@@ -63,8 +71,9 @@ abstract class TreeBasedType<out T : JCTree>(
class TreeBasedPrimitiveType(
tree: JCTree.JCPrimitiveTypeTree,
treePath: TreePath,
javac: JavacWrapper
) : TreeBasedType<JCTree.JCPrimitiveTypeTree>(tree, treePath, javac), JavaPrimitiveType {
javac: JavacWrapper,
annotations: Collection<JavaAnnotation>
) : TreeBasedType<JCTree.JCPrimitiveTypeTree>(tree, treePath, javac, annotations), JavaPrimitiveType {
override val type: PrimitiveType?
get() = if (tree.primitiveTypeKind == TypeKind.VOID) {
@@ -79,22 +88,24 @@ class TreeBasedPrimitiveType(
class TreeBasedArrayType(
tree: JCTree.JCArrayTypeTree,
treePath: TreePath,
javac: JavacWrapper
) : TreeBasedType<JCTree.JCArrayTypeTree>(tree, treePath, javac), JavaArrayType {
javac: JavacWrapper,
annotations: Collection<JavaAnnotation>
) : TreeBasedType<JCTree.JCArrayTypeTree>(tree, treePath, javac, annotations), JavaArrayType {
override val componentType: JavaType
get() = create(tree.elemtype, treePath, javac)
get() = create(tree.elemtype, treePath, javac, annotations)
}
class TreeBasedWildcardType(
tree: JCTree.JCWildcard,
treePath: TreePath,
javac: JavacWrapper
) : TreeBasedType<JCTree.JCWildcard>(tree, treePath, javac), JavaWildcardType {
javac: JavacWrapper,
annotations: Collection<JavaAnnotation>
) : TreeBasedType<JCTree.JCWildcard>(tree, treePath, javac, annotations), JavaWildcardType {
override val bound: JavaType?
get() = tree.bound?.let { create(it, treePath, javac) }
get() = tree.bound?.let { create(it, treePath, javac, annotations) }
override val isExtends: Boolean
get() = tree.kind.kind == BoundKind.EXTENDS
@@ -104,18 +115,47 @@ class TreeBasedWildcardType(
sealed class TreeBasedClassifierType<out T : JCTree>(
tree: T,
treePath: TreePath,
javac: JavacWrapper
) : TreeBasedType<T>(tree, treePath, javac), JavaClassifierType {
javac: JavacWrapper,
annotations: Collection<JavaAnnotation>
) : TreeBasedType<T>(tree, treePath, javac, annotations), JavaClassifierType {
override val classifier: JavaClassifier?
get() = javac.resolve(treePath)
override val classifierQualifiedName: String
get() = (classifier as? JavaClass)?.fqName?.asString() ?: treePath.leaf.toString()
get() = (classifier as? JavaClass)?.fqName?.asString() ?: treePath.leaf.toString().substringBefore("<")
override val presentableText: String
get() = classifierQualifiedName
override val typeArguments: List<JavaType>
get() {
var tree: JCTree = tree
if (tree is JCTree.JCTypeApply) {
tree = tree.clazz
}
if (tree is JCTree.JCFieldAccess) {
val enclosingType = TreeBasedType.create(tree.selected, treePath, javac, annotations)
return (enclosingType as? JavaClassifierType)?.typeArguments ?: emptyList()
}
else {
val classifier = classifier as? JavaClass ?: return emptyList()
if (classifier is MockKotlinClassifier || classifier.isStatic) return emptyList()
return arrayListOf<JavaClass>().apply {
var outer = classifier.outerClass
var staticType = false
while (outer != null && !staticType) {
if (outer.isStatic) {
staticType = true
}
add(outer)
outer = outer.outerClass
}
}.flatMap { it.typeParameters.map(::TreeBasedTypeParameterType) }
}
}
private val typeParameter: JCTree.JCTypeParameter?
get() = treePath.flatMap {
when (it) {
@@ -128,15 +168,36 @@ sealed class TreeBasedClassifierType<out T : JCTree>(
}
class TreeBasedNonGenericClassifierType(
tree: JCTree.JCExpression,
treePath: TreePath,
javac: JavacWrapper
) : TreeBasedClassifierType<JCTree.JCExpression>(tree, treePath, javac) {
class TreeBasedTypeParameterType(override val classifier: JavaTypeParameter) : JavaClassifierType {
override val typeArguments: List<JavaType>
get() = emptyList()
override val isRaw: Boolean
get() = false
override val annotations: Collection<JavaAnnotation>
get() = classifier.annotations.filterTypeAnnotations()
override val classifierQualifiedName: String
get() = classifier.name.asString()
override val presentableText: String
get() = classifierQualifiedName
override fun findAnnotation(fqName: FqName) = annotations.find { it.classId?.asSingleFqName() == fqName }
override val isDeprecatedInJavaDoc: Boolean
get() = false
}
class TreeBasedNonGenericClassifierType(
tree: JCTree.JCExpression,
treePath: TreePath,
javac: JavacWrapper,
annotations: Collection<JavaAnnotation>
) : TreeBasedClassifierType<JCTree.JCExpression>(tree, treePath, javac, annotations) {
override val isRaw: Boolean
get() = (classifier as? MockKotlinClassifier)?.hasTypeParameters
?: (classifier as? JavaClass)?.typeParameters?.isNotEmpty()
@@ -147,13 +208,39 @@ class TreeBasedNonGenericClassifierType(
class TreeBasedGenericClassifierType(
tree: JCTree.JCTypeApply,
treePath: TreePath,
javac: JavacWrapper
) : TreeBasedClassifierType<JCTree.JCTypeApply>(tree, treePath, javac) {
javac: JavacWrapper,
annotations: Collection<JavaAnnotation>
) : TreeBasedClassifierType<JCTree.JCTypeApply>(tree, treePath, javac, annotations) {
override val classifier: JavaClassifier?
get() {
val newTree = tree.clazz
return if (newTree is JCTree.JCAnnotatedType) {
javac.resolve(javac.getTreePath(newTree.underlyingType, treePath.compilationUnit))
}
else super.classifier
}
override val annotations: Collection<JavaAnnotation>
get() {
val newTree = tree.clazz
return (newTree as? JCTree.JCAnnotatedType)?.annotations?.map { TreeBasedAnnotation(it, javac.getTreePath(it, treePath.compilationUnit), javac) }
?.toMutableList<JavaAnnotation>()
?.apply { addAll(super.annotations) }
?: super.annotations
}
override val typeArguments: List<JavaType>
get() = tree.arguments.map { create(it, treePath, javac) }
get() = tree.arguments.map { create(it, treePath, javac, emptyList()) }
.toMutableList()
.apply { addAll(super.typeArguments) }
override val isRaw: Boolean
get() = false
get() = classifier.let {
when (it) {
is MockKotlinClassifier -> tree.arguments.size != it.typeParametersNumber
else -> tree.arguments.size != (classifier as? JavaClass)?.typeParameters?.size
}
}
}
@@ -19,9 +19,15 @@ package org.jetbrains.kotlin.javac.wrappers.trees
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.javac.wrappers.symbols.SymbolBasedArrayAnnotationArgument
import org.jetbrains.kotlin.javac.wrappers.symbols.SymbolBasedField
import org.jetbrains.kotlin.javac.wrappers.symbols.SymbolBasedReferenceAnnotationArgument
import org.jetbrains.kotlin.load.java.JavaVisibilities
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import javax.lang.model.element.Modifier
internal val JCTree.JCModifiers.isAbstract: Boolean
@@ -58,4 +64,38 @@ internal fun JCTree.annotations(): Collection<JCTree.JCAnnotation> = when (this)
} ?: emptyList<JCTree.JCAnnotation>()
fun JavaClass.computeClassId(): ClassId? =
outerClass?.computeClassId()?.createNestedClassId(name) ?: fqName?.let { ClassId.topLevel(it) }
outerClass?.computeClassId()?.createNestedClassId(name) ?: fqName?.let { ClassId.topLevel(it) }
fun Collection<JavaAnnotation>.filterTypeAnnotations(): Collection<JavaAnnotation> {
val filteredAnnotations = arrayListOf<JavaAnnotation>()
for (annotation in this) {
val annotationClass = annotation.resolve()
val targetAnnotation = annotationClass?.annotations?.find { it.classId == ClassId(FqName("java.lang.annotation"), Name.identifier("Target")) } ?: continue
val elementTypeArg = targetAnnotation.arguments.firstOrNull() ?: continue
when (elementTypeArg) {
is SymbolBasedArrayAnnotationArgument -> {
elementTypeArg.args.find { (it as? SymbolBasedReferenceAnnotationArgument)?.element?.simpleName?.contentEquals("TYPE_USE") ?: false }
?.let { filteredAnnotations.add(annotation) }
}
is SymbolBasedReferenceAnnotationArgument -> {
elementTypeArg.element.simpleName.takeIf { it.contentEquals("TYPE_USE") }
?.let { filteredAnnotations.add(annotation) }
}
is TreeBasedArrayAnnotationArgument -> {
elementTypeArg.args.find {
val field = (it as? TreeBasedReferenceAnnotationArgument)?.resolve() as? SymbolBasedField
field?.element?.simpleName?.contentEquals("TYPE_USE") ?: false
}?.let { filteredAnnotations.add(annotation) }
}
is TreeBasedReferenceAnnotationArgument -> {
(elementTypeArg.resolve() as? SymbolBasedField)?.let { field ->
field.element.simpleName.takeIf { it.contentEquals("TYPE_USE") }
?.let { filteredAnnotations.add(annotation) }
}
}
}
}
return filteredAnnotations
}