Introduce custom Java model implementation for class-files

It's only used for CLI compiler, and it should improve performance
of loading Java descriptors from class-files

For IntelliJ, it leads to 10-15% percent speedup of Kotlin Builder

Before this change, we were using a Java model based on Java PSI that
also read class files, but do it less effectively since it performs
some extra work, that we don't need, e.g. eagerly reading all
the inner classes
This commit is contained in:
Denis Zharkov
2017-04-17 12:12:00 +03:00
parent 88950521a8
commit d65af8f951
14 changed files with 1182 additions and 0 deletions
@@ -28,6 +28,10 @@ import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.isNotTopLevelClass
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
@@ -42,6 +46,7 @@ class KotlinCliJavaFileManagerImpl(private val myPsiManager: PsiManager) : CoreJ
private val perfCounter = PerformanceCounter.create("Find Java class")
private var index: JvmDependenciesIndex by Delegates.notNull()
private val topLevelClassesCache: MutableMap<FqName, VirtualFile?> = THashMap()
private val allScope = GlobalSearchScope.allScope(myPsiManager.project)
fun initIndex(packagesCache: JvmDependenciesIndex) {
this.index = packagesCache
@@ -60,8 +65,41 @@ class KotlinCliJavaFileManagerImpl(private val myPsiManager: PsiManager) : CoreJ
}?.takeIf { it in searchScope }
}
private val binaryCache: MutableMap<ClassId, JavaClass?> = THashMap()
private val signatureParsingComponent =
BinaryClassSignatureParser(ClassifierResolutionContext { findClass(it, allScope) })
override fun findClass(classId: ClassId, searchScope: GlobalSearchScope): JavaClass? {
val virtualFile = findVirtualFileForTopLevelClass(classId, searchScope) ?: return null
if (virtualFile.extension == "class") {
// We return all class files' names in the directory in knownClassNamesInPackage method, so one may request an inner class
return binaryCache.getOrPut(classId) {
// Note that currently we implicitly suppose that searchScope for binary classes is constant and we do not use it
// as a key in cache
// This is a true assumption by now since there are two search scopes in compiler: one for sources and another one for binary
// When it become wrong because we introduce the modules into CLI, it's worth to consider
// having different KotlinCliJavaFileManagerImpl's for different modules
val classContent = virtualFile.contentsToByteArray()
if (virtualFile.nameWithoutExtension.contains("$") && isNotTopLevelClass(classContent)) return@getOrPut null
classId.outerClassId?.let { outerClassId ->
val outerClass = findClass(outerClassId, searchScope)
return@getOrPut outerClass?.findInnerClass(classId.shortClassName)
}
val resolver = ClassifierResolutionContext { findClass(it, searchScope) }
BinaryJavaClass(
virtualFile,
classId.asSingleFqName(),
resolver,
signatureParsingComponent,
outerClass = null,
classContent = classContent
)
}
}
return virtualFile.findPsiClassInVirtualFile(classId.relativeClassName.asString())?.let(::JavaClassImpl)
}
@@ -0,0 +1,203 @@
/*
* 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.load.java.structure.impl.classFiles
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
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Type
import java.lang.reflect.Array
internal class AnnotationsCollectorMethodVisitor(
private val member: BinaryJavaMethodBase,
private val context: ClassifierResolutionContext,
private val signatureParser: BinaryClassSignatureParser,
private val parametersToSkipNumber: Int
) : MethodVisitor(ASM_API_VERSION_FOR_CLASS_READING) {
override fun visitAnnotationDefault(): AnnotationVisitor? {
member.safeAs<BinaryJavaMethod>()?.hasAnnotationParameterDefaultValue = true
// We don't store default value in Java model
return null
}
override fun visitAnnotation(desc: String, visible: Boolean) =
BinaryJavaAnnotation.addAnnotation(
member.annotations as MutableCollection<JavaAnnotation>,
desc, context, signatureParser
)
override fun visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor? {
val index = parameter - parametersToSkipNumber
if (index < 0) return null
val annotations =
member.valueParameters[index].annotations as MutableCollection<JavaAnnotation>?
?: return null
return BinaryJavaAnnotation.addAnnotation(annotations, desc, context, signatureParser)
}
}
class BinaryJavaAnnotation private constructor(
desc: String,
private val context: ClassifierResolutionContext,
override val arguments: Collection<JavaAnnotationArgument>
) : JavaAnnotation {
companion object {
fun createAnnotationAndVisitor(
desc: String,
context: ClassifierResolutionContext,
signatureParser: BinaryClassSignatureParser
): Pair<JavaAnnotation, AnnotationVisitor> {
val arguments = mutableListOf<JavaAnnotationArgument>()
val annotation = BinaryJavaAnnotation(desc, context, arguments)
return annotation to BinaryJavaAnnotationVisitor(context, signatureParser, arguments)
}
fun addAnnotation(
annotations: MutableCollection<JavaAnnotation>,
desc: String,
context: ClassifierResolutionContext,
signatureParser: BinaryClassSignatureParser
): AnnotationVisitor {
val (javaAnnotation, annotationVisitor) = createAnnotationAndVisitor(desc, context, signatureParser)
annotations.add(javaAnnotation)
return annotationVisitor
}
}
private val classifierResolutionResult by lazy(LazyThreadSafetyMode.NONE) {
context.resolveByInternalName(Type.getType(desc).internalName)
}
override val classId: ClassId?
get() = classifierResolutionResult.classifier.safeAs<JavaClass>()?.classId()
?: ClassId.topLevel(FqName(classifierResolutionResult.qualifiedName))
override fun resolve() = classifierResolutionResult.classifier as? JavaClass
}
class BinaryJavaAnnotationVisitor(
private val context: ClassifierResolutionContext,
private val signatureParser: BinaryClassSignatureParser,
private val arguments: MutableCollection<JavaAnnotationArgument>
) : AnnotationVisitor(ASM_API_VERSION_FOR_CLASS_READING) {
private fun addArgument(argument: JavaAnnotationArgument?) {
arguments.addIfNotNull(argument)
}
override fun visitAnnotation(name: String?, desc: String): AnnotationVisitor {
val (annotation, visitor) =
BinaryJavaAnnotation.createAnnotationAndVisitor(desc, context, signatureParser)
arguments.add(PlainJavaAnnotationAsAnnotationArgument(name, annotation))
return visitor
}
override fun visitEnum(name: String?, desc: String, value: String) {
addArgument(PlainJavaEnumValueAnnotationArgument(name, desc, value, context))
}
override fun visit(name: String?, value: Any?) {
addArgument(convertConstValue(name, value))
}
private fun convertConstValue(name: String?, value: Any?): JavaAnnotationArgument? {
return when (value) {
is Byte, is Boolean, is Char, is Short, is Int, is Long, is Float, is Double, is String ->
PlainJavaLiteralAnnotationArgument(name, value)
is Type -> PlainJavaClassObjectAnnotationArgument(name, value, signatureParser, context)
else -> value?.takeIf { it.javaClass.isArray }?.let { array ->
val arguments = (0 until Array.getLength(array)).mapNotNull { index ->
convertConstValue(name = null, value = Array.get(array, index))
}
PlainJavaArrayAnnotationArgument(name, arguments)
}
}
}
override fun visitArray(name: String?): AnnotationVisitor {
val result = mutableListOf<JavaAnnotationArgument>()
addArgument(PlainJavaArrayAnnotationArgument(name, result))
return BinaryJavaAnnotationVisitor(context, signatureParser, result)
}
}
abstract class PlainJavaAnnotationArgument(name: String?) : JavaAnnotationArgument {
override val name: Name? = name?.takeIf(Name::isValidIdentifier)?.let(Name::identifier)
}
class PlainJavaLiteralAnnotationArgument(
name: String?,
override val value: Any?
) : PlainJavaAnnotationArgument(name), JavaLiteralAnnotationArgument
class PlainJavaClassObjectAnnotationArgument(
name: String?,
private val type: Type,
private val signatureParser: BinaryClassSignatureParser,
private val context: ClassifierResolutionContext
) : PlainJavaAnnotationArgument(name), JavaClassObjectAnnotationArgument {
override fun getReferencedType() = signatureParser.mapAsmType(type, context)
}
class PlainJavaArrayAnnotationArgument(
name: String?,
private val elements: List<JavaAnnotationArgument>
) : PlainJavaAnnotationArgument(name), JavaArrayAnnotationArgument {
override fun getElements(): List<JavaAnnotationArgument> = elements
}
class PlainJavaAnnotationAsAnnotationArgument(
name: String?,
private val annotation: JavaAnnotation
) : PlainJavaAnnotationArgument(name), JavaAnnotationAsAnnotationArgument {
override fun getAnnotation() = annotation
}
class PlainJavaEnumValueAnnotationArgument(
name: String?,
private val desc: String,
private val entryName: String,
private val context: ClassifierResolutionContext
) : PlainJavaAnnotationArgument(name), JavaEnumValueAnnotationArgument {
override fun resolve(): JavaField? {
val javaClass = context.resolveByInternalName(Type.getType(desc).internalName).classifier as? JavaClass ?: return null
return javaClass.fields.singleOrNull { it.name.asString() == entryName }
}
}
private fun JavaClass.classId(): ClassId? {
val fqName = fqName ?: return null
if (outerClass == null) return ClassId.topLevel(fqName)
val outerClassId = outerClass!!.classId() ?: return null
return ClassId(outerClassId.packageFqName, outerClassId.relativeClassName.child(name), false)
}
@@ -0,0 +1,232 @@
/*
* 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.load.java.structure.impl.classFiles
import com.intellij.psi.CommonClassNames
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.load.java.structure.JavaClassifierType
import org.jetbrains.kotlin.load.java.structure.JavaType
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.org.objectweb.asm.Type
import java.text.CharacterIterator
import java.text.StringCharacterIterator
/**
* Take a look at com.intellij.psi.impl.compiled.SignatureParsing
*/
class BinaryClassSignatureParser(globalContext: ClassifierResolutionContext) {
companion object {
private val JAVA_LANG_OBJECT = ClassId.topLevel(FqName(CommonClassNames.JAVA_LANG_OBJECT))
}
private val JAVA_LANG_OBJECT_CLASSIFIER_TYPE: JavaClassifierType =
PlainJavaClassifierType({ globalContext.resolveClass(JAVA_LANG_OBJECT) }, emptyList())
fun parseTypeParametersDeclaration(signature: CharacterIterator, context: ClassifierResolutionContext): List<JavaTypeParameter> {
if (signature.current() != '<') {
return emptyList()
}
val typeParameters = ContainerUtil.newArrayList<JavaTypeParameter>()
signature.next()
while (signature.current() != '>') {
typeParameters.add(parseTypeParameter(signature, context))
}
signature.next()
return typeParameters
}
private fun parseTypeParameter(signature: CharacterIterator, context: ClassifierResolutionContext): JavaTypeParameter {
val name = StringBuilder()
while (signature.current() != ':' && signature.current() != CharacterIterator.DONE) {
name.append(signature.current())
signature.next()
}
if (signature.current() == CharacterIterator.DONE) {
throw ClsFormatException()
}
val parameterName = name.toString()
// postpone list allocation till a second bound is seen; ignore sole Object bound
var bounds: MutableList<JavaClassifierType>? = null
var jlo = false
while (signature.current() == ':') {
signature.next()
val bound = parseClassifierRefSignature(signature, context) ?: continue
if (bounds == null) {
if (JAVA_LANG_OBJECT_CLASSIFIER_TYPE === bound) {
jlo = true
continue
}
bounds = ContainerUtil.newSmartList()
if (jlo) {
bounds.add(JAVA_LANG_OBJECT_CLASSIFIER_TYPE)
}
}
bounds.add(bound)
}
return BinaryJavaTypeParameter(Name.identifier(parameterName), bounds ?: emptyList())
}
fun parseClassifierRefSignature(signature: CharacterIterator, context: ClassifierResolutionContext): JavaClassifierType? {
return when (signature.current()) {
'L' -> parseParameterizedClassRefSignature(signature, context)
'T' -> parseTypeVariableRefSignature(signature, context)
else -> null
}
}
private fun parseTypeVariableRefSignature(signature: CharacterIterator, context: ClassifierResolutionContext): JavaClassifierType? {
val id = StringBuilder()
signature.next()
while (signature.current() != ';' && signature.current() != '>' && signature.current() != CharacterIterator.DONE) {
id.append(signature.current())
signature.next()
}
if (signature.current() == CharacterIterator.DONE) {
throw ClsFormatException()
}
if (signature.current() == ';') {
signature.next()
}
return PlainJavaClassifierType({ context.resolveTypeParameter(id.toString()) }, emptyList())
}
private fun parseParameterizedClassRefSignature(
signature: CharacterIterator,
context: ClassifierResolutionContext
): JavaClassifierType {
val canonicalName = StringBuilder()
val argumentGroups = ContainerUtil.newSmartList<List<JavaType>>()
signature.next()
while (signature.current() != ';' && signature.current() != CharacterIterator.DONE) {
val c = signature.current()
if (c == '<') {
val group = mutableListOf<JavaType>()
signature.next()
do {
group.add(parseClassOrTypeVariableElement(signature, context))
}
while (signature.current() != '>')
argumentGroups.add(group)
}
else if (c != ' ') {
canonicalName.append(c)
}
signature.next()
}
if (signature.current() == CharacterIterator.DONE) {
throw ClsFormatException()
}
signature.next()
if (canonicalName.toString() == "java/lang/Object") return JAVA_LANG_OBJECT_CLASSIFIER_TYPE
return PlainJavaClassifierType({ context.resolveByInternalName(canonicalName.toString()) }, argumentGroups.reversed().flatten())
}
private fun parseClassOrTypeVariableElement(signature: CharacterIterator, context: ClassifierResolutionContext): JavaType {
val variance = parseVariance(signature)
if (variance == JavaSignatureVariance.STAR) {
return PlainJavaWildcardType(bound = null, isExtends = true)
}
val type = parseTypeString(signature, context)
if (variance == JavaSignatureVariance.NO_VARIANCE) return type
return PlainJavaWildcardType(type, isExtends = variance == JavaSignatureVariance.PLUS)
}
private enum class JavaSignatureVariance {
PLUS, MINUS, STAR, NO_VARIANCE
}
private fun parseVariance(signature: CharacterIterator): JavaSignatureVariance {
var advance = true
val variance = when (signature.current()) {
'+' -> JavaSignatureVariance.PLUS
'-' -> JavaSignatureVariance.MINUS
'*' -> JavaSignatureVariance.STAR
'.', '=' -> JavaSignatureVariance.NO_VARIANCE
else -> {
advance = false
JavaSignatureVariance.NO_VARIANCE
}
}
if (advance) {
signature.next()
}
return variance
}
private fun parseDimensions(signature: CharacterIterator): Int {
var dimensions = 0
while (signature.current() == '[') {
dimensions++
signature.next()
}
return dimensions
}
fun parseTypeString(signature: CharacterIterator, context: ClassifierResolutionContext): JavaType {
val dimensions = parseDimensions(signature)
val type: JavaType = parseTypeWithoutVarianceAndArray(signature, context) ?: throw ClsFormatException()
return (1..dimensions).fold(type) { result, _ -> PlainJavaArrayType(result) }
}
fun mapAsmType(type: Type, context: ClassifierResolutionContext) = parseTypeString(StringCharacterIterator(type.descriptor), context)
private fun parseTypeWithoutVarianceAndArray(signature: CharacterIterator, context: ClassifierResolutionContext) =
when (signature.current()) {
'L' -> parseParameterizedClassRefSignature(signature, context)
'T' -> parseTypeVariableRefSignature(signature, context)
'B' -> parsePrimitiveType(signature, PrimitiveType.BYTE)
'C' -> parsePrimitiveType(signature, PrimitiveType.CHAR)
'D' -> parsePrimitiveType(signature, PrimitiveType.DOUBLE)
'F' -> parsePrimitiveType(signature, PrimitiveType.FLOAT)
'I' -> parsePrimitiveType(signature, PrimitiveType.INT)
'J' -> parsePrimitiveType(signature, PrimitiveType.LONG)
'Z' -> parsePrimitiveType(signature, PrimitiveType.BOOLEAN)
'S' -> parsePrimitiveType(signature, PrimitiveType.SHORT)
'V' -> parsePrimitiveType(signature, null)
else -> null
}
private fun parsePrimitiveType(signature: CharacterIterator, primitiveType: PrimitiveType?): JavaType {
signature.next()
return PlainJavaPrimitiveType(primitiveType)
}
class ClsFormatException(message: String? = null, cause: Throwable? = null) : Throwable(message, cause)
}
@@ -0,0 +1,194 @@
/*
* 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.load.java.structure.impl.classFiles
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ContainerUtil
import gnu.trove.THashMap
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.org.objectweb.asm.*
import java.text.CharacterIterator
import java.text.StringCharacterIterator
class BinaryJavaClass(
val virtualFile: VirtualFile,
override val fqName: FqName,
private val context: ClassifierResolutionContext,
private val signatureParser: BinaryClassSignatureParser,
override var access: Int = 0,
override val outerClass: JavaClass?,
classContent: ByteArray? = null
) : ClassVisitor(ASM_API_VERSION_FOR_CLASS_READING), JavaClass, BinaryJavaModifierListOwner, BinaryJavaAnnotationOwner {
lateinit var myInternalName: String
override val annotations: MutableCollection<JavaAnnotation> = mutableListOf()
override lateinit var typeParameters: List<JavaTypeParameter>
override lateinit var supertypes: Collection<JavaClassifierType>
override val methods = arrayListOf<JavaMethod>()
override val fields = arrayListOf<JavaField>()
override val constructors = arrayListOf<JavaConstructor>()
override val annotationsByFqName by buildLazyValueForMap()
private val innerClassNameToAccess: MutableMap<Name, Int> = THashMap()
override val innerClassNames get() = innerClassNameToAccess.keys
override val name: Name
get() = fqName.shortName()
override val isInterface get() = isSet(Opcodes.ACC_INTERFACE)
override val isAnnotationType get() = isSet(Opcodes.ACC_ANNOTATION)
override val isEnum get() = isSet(Opcodes.ACC_ENUM)
override val lightClassOriginKind: LightClassOriginKind? get() = null
override fun visitEnd() {
methods.trimToSize()
fields.trimToSize()
constructors.trimToSize()
}
init {
ClassReader(classContent ?: virtualFile.contentsToByteArray()).accept(
this,
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
)
}
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
if (access.isSet(Opcodes.ACC_SYNTHETIC) || access.isSet(Opcodes.ACC_BRIDGE) || name == "<clinit>") return null
// skip semi-synthetic enum methods
if (isEnum) {
if (name == "values" && desc.startsWith("()")) return null
if (name == "valueOf" && desc.startsWith("(Ljava/lang/String;)")) return null
}
val (member, visitor) = BinaryJavaMethodBase.create(name, access, desc, signature, this, context.copyForMember(), signatureParser)
when (member) {
is JavaMethod -> methods.add(member)
is JavaConstructor -> constructors.add(member)
else -> error("Unexpected member: ${member.javaClass}")
}
return visitor
}
override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
if (access.isSet(Opcodes.ACC_SYNTHETIC)) return
if (innerName == null || outerName == null) return
if (myInternalName == outerName) {
context.addInnerClass(name, outerName, innerName)
innerClassNameToAccess[context.mapInternalNameToClassId(name).shortClassName] = access
}
}
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
this.access = this.access or access
this.myInternalName = name
if (signature != null) {
parseClassSignature(signature)
}
else {
this.typeParameters = emptyList()
this.supertypes = mutableListOf<JavaClassifierType>().apply {
addIfNotNull(superName?.convertInternalNameToClassifierType())
interfaces?.forEach {
addIfNotNull(it.convertInternalNameToClassifierType())
}
}
}
}
private fun parseClassSignature(signature: String) {
val iterator = StringCharacterIterator(signature)
this.typeParameters =
signatureParser
.parseTypeParametersDeclaration(iterator, context)
.also(context::addTypeParameters)
val supertypes = ContainerUtil.newSmartList<JavaClassifierType>()
supertypes.addIfNotNull(signatureParser.parseClassifierRefSignature(iterator, context))
while (iterator.current() != CharacterIterator.DONE) {
supertypes.addIfNotNull(signatureParser.parseClassifierRefSignature(iterator, context))
}
this.supertypes = supertypes
}
private fun String.convertInternalNameToClassifierType(): JavaClassifierType =
PlainJavaClassifierType({ context.resolveByInternalName(this) }, emptyList())
override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? {
if (access.isSet(Opcodes.ACC_SYNTHETIC)) return null
val type = signatureParser.parseTypeString(StringCharacterIterator(signature ?: desc), context)
val processedValue = processValue(value, type)
return BinaryJavaField(Name.identifier(name), access, this, access.isSet(Opcodes.ACC_ENUM), type, processedValue).run {
fields.add(this)
object : FieldVisitor(ASM_API_VERSION_FOR_CLASS_READING) {
override fun visitAnnotation(desc: String, visible: Boolean) =
BinaryJavaAnnotation.addAnnotation(this@run.annotations, desc, context, signatureParser)
}
}
}
/**
* All the int-like values (including Char/Boolean) come in visitor as Integer instances
*/
private fun processValue(value: Any?, fieldType: JavaType): Any? {
if (fieldType !is JavaPrimitiveType || fieldType.type == null || value !is Int) return value
return when (fieldType.type) {
PrimitiveType.BOOLEAN -> {
when (value) {
0 -> false
1 -> true
else -> value
}
}
PrimitiveType.CHAR -> value.toChar()
else -> value
}
}
override fun visitAnnotation(desc: String, visible: Boolean) =
BinaryJavaAnnotation.addAnnotation(annotations, desc, context, signatureParser)
override fun findInnerClass(name: Name): JavaClass? {
val access = innerClassNameToAccess[name] ?: return null
return virtualFile.parent.findChild("${virtualFile.nameWithoutExtension}$$name.class")?.let {
BinaryJavaClass(it, fqName.child(name), context.copyForMember(), signatureParser, access, this)
}
}
}
@@ -0,0 +1,120 @@
/*
* 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.load.java.structure.impl.classFiles
import com.intellij.util.containers.ContainerUtil
import gnu.trove.THashMap
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaClassifier
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
typealias ClassIdToJavaClass = (ClassId) -> JavaClass?
class ClassifierResolutionContext private constructor(
private val classesByQName: ClassIdToJavaClass,
// Note that this data is fully mutable and its correctness is based on the assumption
// that nobody starts resolving classifier until type parameters and inner classes are initialized.
// Currently it's implemented through laziness in the PlainJavaClassifierType.
private var typeParameters: MutableMap<String, JavaTypeParameter>?,
private var innerClasses: MutableMap<String, InnerClassInfo>?
) {
constructor(classesByQName: ClassIdToJavaClass) : this(classesByQName, null, null)
internal data class Result(val classifier: JavaClassifier?, val qualifiedName: String)
private class InnerClassInfo(val outerInternalName: String, val simpleName: String)
internal fun addInnerClass(innerInternalName: String, outerInternalName: String, simpleName: String) {
if (innerClasses == null) {
innerClasses = THashMap()
}
innerClasses!!.put(innerInternalName, InnerClassInfo(outerInternalName, simpleName))
}
internal fun addTypeParameters(newTypeParameters: Collection<JavaTypeParameter>) {
if (newTypeParameters.isEmpty()) return
if (typeParameters == null) {
typeParameters = THashMap()
}
newTypeParameters.associateByTo(typeParameters!!) { it.name.identifier }
}
internal fun resolveClass(classId: ClassId) = Result(classesByQName(classId), classId.asSingleFqName().asString())
internal fun resolveTypeParameter(name: String) = Result(typeParameters?.get(name), name)
internal fun copyForMember() =
ClassifierResolutionContext(classesByQName, typeParameters?.let(::THashMap), innerClasses?.let(::THashMap))
// See com.intellij.psi.impl.compiled.StubBuildingVisitor.createMapping(byte[])
internal fun mapInternalNameToClassId(internalName: String): ClassId {
if ('.' in internalName) {
val parts = internalName.split('.')
val outerClass = mapInternalNameToClassId(parts[0])
val nestedParts = parts.subList(1, parts.size)
return nestedParts.fold(outerClass) { classId, part ->
classId.createNestedClassId(Name.identifier(part))
}
}
if ('$' in internalName) {
val innerClassInfo = innerClasses?.get(internalName) ?: return mapInternalNameToClassIdNaively(internalName)
if (Name.isValidIdentifier(innerClassInfo.simpleName)) {
val outerClassId = mapInternalNameToClassId(innerClassInfo.outerInternalName)
return outerClassId.createNestedClassId(Name.identifier(innerClassInfo.simpleName))
}
}
return createClassIdForTopLevel(internalName)
}
// See com.intellij.psi.impl.compiled.StubBuildingVisitor.GUESSING_MAPPER
private fun mapInternalNameToClassIdNaively(internalName: String): ClassId {
val splitPoints = ContainerUtil.newSmartList<Int>()
for (p in 0..internalName.length - 1) {
val c = internalName[p]
if (c == '$' && p > 0 && internalName[p - 1] != '/' && p < internalName.length - 1 && internalName[p + 1] != '$') {
splitPoints.add(p)
}
}
if (splitPoints.isNotEmpty()) {
val substrings = (listOf(-1) + splitPoints).zip(splitPoints + internalName.length).map { (from, to) ->
internalName.substring(from + 1, to)
}
val outerFqName = FqName(substrings[0].replace('/', '.'))
val packageFqName = outerFqName.parent()
val relativeName =
FqName(outerFqName.shortName().asString() + "." + substrings.subList(1, substrings.size).joinToString("."))
return ClassId(packageFqName, relativeName, false)
}
return createClassIdForTopLevel(internalName)
}
private fun createClassIdForTopLevel(internalName: String) = ClassId.topLevel(FqName(internalName.replace('/', '.')))
internal fun resolveByInternalName(c: String) = resolveClass(mapInternalNameToClassId(c))
}
@@ -0,0 +1,170 @@
/*
* 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.load.java.structure.impl.classFiles
import com.intellij.util.cls.ClsFormatException
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import java.text.CharacterIterator
import java.text.StringCharacterIterator
abstract class BinaryJavaMethodBase(
override val access: Int,
override val containingClass: JavaClass,
val valueParameters: List<JavaValueParameter>,
val typeParameters: List<JavaTypeParameter>,
override val name: Name
) : JavaMember, BinaryJavaAnnotationOwner, BinaryJavaModifierListOwner {
override val annotationsByFqName by buildLazyValueForMap()
override val annotations: Collection<JavaAnnotation> = mutableListOf()
companion object {
private class MethodInfo(
val returnType: JavaType,
val typeParameters: List<JavaTypeParameter>,
val valueParameterTypes: List<JavaType>
)
fun create(
name: String,
access: Int,
desc: String,
signature: String?,
containingClass: JavaClass,
parentContext: ClassifierResolutionContext,
signatureParser: BinaryClassSignatureParser
): Pair<JavaMember, MethodVisitor> {
val isConstructor = "<init>" == name
val isVarargs = access.isSet(Opcodes.ACC_VARARGS)
val isInnerClassConstructor = isConstructor && containingClass.outerClass != null && !containingClass.isStatic
val isEnumConstructor = containingClass.isEnum && isConstructor
val info: MethodInfo =
if (signature != null) {
val contextForMethod = parentContext.copyForMember()
parseMethodSignature(signature, signatureParser, contextForMethod).also {
contextForMethod.addTypeParameters(it.typeParameters)
}
} else
parseMethodDescription(desc, parentContext, signatureParser).let {
when {
isEnumConstructor ->
// skip ordinal/name parameters for enum constructors
MethodInfo(it.returnType, it.typeParameters, it.valueParameterTypes.drop(2))
isInnerClassConstructor ->
// omit synthetic inner class constructor parameter
MethodInfo(it.returnType, it.typeParameters, it.valueParameterTypes.drop(1))
else -> it
}
}
val parameterTypes = info.valueParameterTypes
val parameterList = ContainerUtil.newSmartList<JavaValueParameter>()
val paramCount = parameterTypes.size
for (i in 0..paramCount - 1) {
val type = parameterTypes[i]
val isEllipsisParam = isVarargs && i == paramCount - 1
parameterList.add(BinaryJavaValueParameter(null, type, isEllipsisParam))
}
val member: BinaryJavaMethodBase =
if (isConstructor)
BinaryJavaConstructor(access, containingClass, parameterList, info.typeParameters)
else
BinaryJavaMethod(
access, containingClass, parameterList, info.typeParameters, Name.identifier(name), info.returnType
)
val paramIgnoreCount = when {
isEnumConstructor -> 2
isInnerClassConstructor -> 1
else -> 0
}
return member to AnnotationsCollectorMethodVisitor(member, parentContext, signatureParser, paramIgnoreCount)
}
private fun parseMethodDescription(
desc: String,
context: ClassifierResolutionContext,
signatureParser: BinaryClassSignatureParser
): MethodInfo {
val returnType = signatureParser.mapAsmType(Type.getReturnType(desc), context)
val parameterTypes = Type.getArgumentTypes(desc).map { signatureParser.mapAsmType(it, context) }
return MethodInfo(returnType, emptyList(), parameterTypes)
}
private fun parseMethodSignature(
signature: String,
signatureParser: BinaryClassSignatureParser,
context: ClassifierResolutionContext
): MethodInfo {
val iterator = StringCharacterIterator(signature)
val typeParameters = signatureParser.parseTypeParametersDeclaration(iterator, context)
if (iterator.current() != '(') throw ClsFormatException()
iterator.next()
val paramTypes: List<JavaType>
if (iterator.current() == ')') {
paramTypes = emptyList()
}
else {
paramTypes = ContainerUtil.newSmartList()
while (iterator.current() != ')' && iterator.current() != CharacterIterator.DONE) {
paramTypes.add(signatureParser.parseTypeString(iterator, context))
}
if (iterator.current() != ')') throw ClsFormatException()
}
iterator.next()
val returnType = signatureParser.parseTypeString(iterator, context)
return MethodInfo(returnType, typeParameters, paramTypes)
}
}
}
class BinaryJavaMethod(
flags: Int,
containingClass: JavaClass,
valueParameters: List<JavaValueParameter>,
typeParameters: List<JavaTypeParameter>,
name: Name,
override val returnType: JavaType
) : BinaryJavaMethodBase(
flags, containingClass, valueParameters, typeParameters, name
), JavaMethod {
override var hasAnnotationParameterDefaultValue: Boolean = false
}
class BinaryJavaConstructor(
flags: Int,
containingClass: JavaClass,
valueParameters: List<JavaValueParameter>,
typeParameters: List<JavaTypeParameter>
) : BinaryJavaMethodBase(
flags, containingClass, valueParameters, typeParameters,
SpecialNames.NO_NAME_PROVIDED
), JavaConstructor
@@ -0,0 +1,81 @@
/*
* 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.load.java.structure.impl.classFiles
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
class BinaryJavaField(
override val name: Name,
override val access: Int,
override val containingClass: JavaClass,
override val isEnumEntry: Boolean,
override val type: JavaType,
val compiledValue: Any?
) : JavaField, BinaryJavaAnnotationOwner, BinaryJavaModifierListOwner {
override val annotations: MutableCollection<JavaAnnotation> = ContainerUtil.newSmartList()
override val annotationsByFqName by buildLazyValueForMap()
}
class BinaryJavaTypeParameter(
override val name: Name,
override val upperBounds: Collection<JavaClassifierType>
) : JavaTypeParameter {
// TODO: support annotations on type parameters
override val annotations get() = emptyList<JavaAnnotation>()
override fun findAnnotation(fqName: FqName) = null
override val isDeprecatedInJavaDoc get() = false
}
class BinaryJavaValueParameter(
override val name: Name?,
override val type: JavaType,
override val isVararg: Boolean
) : JavaValueParameter, BinaryJavaAnnotationOwner {
override val annotations: MutableCollection<JavaAnnotation> = ContainerUtil.newSmartList()
override val annotationsByFqName by buildLazyValueForMap()
}
fun isNotTopLevelClass(classContent: ByteArray): Boolean {
var isNotTopLevelClass = false
ClassReader(classContent).accept(
object : ClassVisitor(ASM_API_VERSION_FOR_CLASS_READING) {
private var internalName: String? = null
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
internalName = name
}
override fun visitInnerClass(name: String?, outerName: String?, innerName: String?, access: Int) {
isNotTopLevelClass = isNotTopLevelClass or (name == internalName)
}
},
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
)
return isNotTopLevelClass
}
@@ -0,0 +1,54 @@
/*
* 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.load.java.structure.impl.classFiles
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
// They are only used for java class files, but potentially may be used in other cases
// It would be better to call them like JavaSomeTypeImpl, but these names are already occupied by the PSI based types
internal class PlainJavaArrayType(override val componentType: JavaType) : JavaArrayType
internal class PlainJavaWildcardType(override val bound: JavaType?, override val isExtends: Boolean) : JavaWildcardType
internal class PlainJavaPrimitiveType(override val type: PrimitiveType?) : JavaPrimitiveType
internal class PlainJavaClassifierType(
// calculation of classifier and canonicalText
classifierComputation: () -> ClassifierResolutionContext.Result,
override val typeArguments: List<JavaType>
) : JavaClassifierType {
private val classifierResolverResult by lazy(LazyThreadSafetyMode.NONE, classifierComputation)
override val classifier get() = classifierResolverResult.classifier
override val isRaw
get() = typeArguments.isEmpty() &&
classifierResolverResult.classifier?.safeAs<JavaClass>()?.typeParameters?.isNotEmpty() == true
// TODO: support type annotations
override val annotations get() = emptyList<JavaAnnotation>()
override fun findAnnotation(fqName: FqName) = null
override val isDeprecatedInJavaDoc get() = false
override val classifierQualifiedName: String
get() = classifierResolverResult.qualifiedName
// TODO: render arguments for presentable text
override val presentableText: String
get() = classifierQualifiedName
}
@@ -0,0 +1,61 @@
/*
* 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.load.java.structure.impl.classFiles
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.load.java.JavaVisibilities
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaAnnotationOwner
import org.jetbrains.kotlin.load.java.structure.JavaModifierListOwner
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.org.objectweb.asm.Opcodes
internal val ASM_API_VERSION_FOR_CLASS_READING = Opcodes.ASM5
internal interface BinaryJavaAnnotationOwner : JavaAnnotationOwner {
val annotationsByFqName: Map<FqName, JavaAnnotation>
override fun findAnnotation(fqName: FqName) = annotationsByFqName[fqName]
override val isDeprecatedInJavaDoc: Boolean
get() = false
}
internal interface BinaryJavaModifierListOwner : JavaModifierListOwner, BinaryJavaAnnotationOwner {
val access: Int
fun isSet(flag: Int) = access.isSet(flag)
override val isAbstract get() = isSet(Opcodes.ACC_ABSTRACT)
override val isStatic get() = isSet(Opcodes.ACC_STATIC)
override val isFinal get() = isSet(Opcodes.ACC_FINAL)
override val visibility: Visibility
get() = when {
isSet(Opcodes.ACC_PRIVATE) -> Visibilities.PRIVATE
isSet(Opcodes.ACC_PROTECTED) ->
if (isStatic) JavaVisibilities.PROTECTED_STATIC_VISIBILITY else JavaVisibilities.PROTECTED_AND_PACKAGE
isSet(Opcodes.ACC_PUBLIC) -> Visibilities.PUBLIC
else -> JavaVisibilities.PACKAGE_VISIBILITY
}
override val isDeprecatedInJavaDoc get() = isSet(Opcodes.ACC_DEPRECATED)
}
internal fun JavaAnnotationOwner.buildLazyValueForMap() = lazy {
annotations.filter { it.classId != null }.associateBy({ it.classId!!.asSingleFqName() }, { it })
}
internal fun Int.isSet(flag: Int) = this and flag != 0
@@ -0,0 +1,5 @@
package test;
public class TopLevel$Class {
public void foo(TopLevel$Class other) {}
}
@@ -0,0 +1,6 @@
package test
public open class `TopLevel$Class` {
public constructor `TopLevel$Class`()
public open fun foo(/*0*/ test.`TopLevel$Class`!): kotlin.Unit
}
@@ -0,0 +1,6 @@
package test
public open class `TopLevel$Class` {
public constructor `TopLevel$Class`()
public open fun foo(/*0*/ p0: test.TopLevel.Class!): kotlin.Unit
}
@@ -284,6 +284,12 @@ public class LoadJavaTestGenerated extends AbstractLoadJavaTest {
doTestCompiledJava(fileName);
}
@TestMetadata("TopLevel$Class.java")
public void testTopLevel$Class() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/TopLevel$Class.java");
doTestCompiledJava(fileName);
}
@TestMetadata("TwoFields.java")
public void testTwoFields() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/TwoFields.java");
@@ -3410,6 +3410,12 @@ public class JvmRuntimeDescriptorLoaderTestGenerated extends AbstractJvmRuntimeD
doTest(fileName);
}
@TestMetadata("TopLevel$Class.java")
public void testTopLevel$Class() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/TopLevel$Class.java");
doTest(fileName);
}
@TestMetadata("TwoFields.java")
public void testTwoFields() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/TwoFields.java");