Files
kotlin-fork/compiler/tests/org/jetbrains/kotlin/checkers/LazyOperationsLog.kt
T
2015-11-06 01:12:00 +03:00

220 lines
8.0 KiB
Kotlin

/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers
import org.jetbrains.kotlin.descriptors.Named
import org.jetbrains.kotlin.load.java.structure.JavaNamedElement
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.load.java.structure.impl.JavaTypeImpl
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.debugText.getDebugText
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate
import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionTaskHolder
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.deserialization.DeserializationContext
import org.jetbrains.kotlin.serialization.deserialization.TypeDeserializer
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.KotlinTypeImpl
import org.jetbrains.kotlin.utils.Printer
import java.lang.reflect.Constructor
import java.lang.reflect.GenericDeclaration
import java.lang.reflect.Method
import java.util.*
import java.util.regex.Pattern
class LazyOperationsLog(
val stringSanitizer: (String) -> String
) {
val ids = IdentityHashMap<Any, Int>()
private fun objectId(o: Any): Int = ids.getOrPut(o, { ids.size() })
private class Record(
val lambda: Any,
val data: LoggingStorageManager.CallData
)
private val records = ArrayList<Record>()
public val addRecordFunction: (lambda: Any, LoggingStorageManager.CallData) -> Unit = {
lambda, data ->
records.add(Record(lambda, data))
}
public fun getText(): String {
val groupedByOwner = records.groupByTo(IdentityHashMap()) {
it.data.fieldOwner
}.map { Pair(it.getKey(), it.getValue()) }
return groupedByOwner.map {
val (owner, records) = it
renderOwner(owner, records)
}.sortedBy(stringSanitizer).joinToString("\n").renumberObjects()
}
/**
* Replaces ids in the given string so that they increase
* Example:
* input = "A@21 B@6"
* output = "A@0 B@1"
*/
private fun String.renumberObjects(): String {
val ids = HashMap<String, String>()
fun newId(objectId: String): String {
return ids.getOrPut(objectId, { "@" + ids.size() })
}
val m = Pattern.compile("@\\d+").matcher(this)
val sb = StringBuffer()
while (m.find()) {
m.appendReplacement(sb, newId(m.group(0)))
}
m.appendTail(sb)
return sb.toString()
}
private fun renderOwner(owner: Any?, records: List<Record>): String {
val sb = StringBuilder()
with (Printer(sb)) {
println(render(owner), " {")
indent {
records.map { renderRecord(it) }.sortedBy(stringSanitizer).forEach {
println(it)
}
}
println("}")
}
return sb.toString()
}
private fun renderRecord(record: Record): String {
val data = record.data
val sb = StringBuilder()
sb.append(data.field?.getName() ?: "in ${data.lambdaCreatedIn.getDeclarationName()}")
if (!data.arguments.isEmpty()) {
data.arguments.joinTo(sb, ", ", "(", ")") { render(it) }
}
sb.append(" = ${render(data.result)}")
if (data.fieldOwner is MemberScope) {
sb.append(" // through ${render(data.fieldOwner)}")
}
return sb.toString()
}
private fun render(o: Any?): String {
if (o == null) return "null"
val sb = StringBuilder()
if (o is FqName || o is Name || o is String || o is Number || o is Boolean) {
sb.append("'$o': ")
}
val id = objectId(o)
val aClass = o.javaClass
sb.append(if (aClass.isAnonymousClass()) aClass.getName().substringAfterLast('.') else aClass.getSimpleName()).append("@$id")
fun Any.appendQuoted() {
sb.append("['").append(this).append("']")
}
when {
o is Named -> o.getName().appendQuoted()
o.javaClass.getSimpleName() == "LazyJavaClassifierType" -> {
val javaType = o.field<JavaTypeImpl<*>>("javaType")
javaType.getPsi().getPresentableText().appendQuoted()
}
o.javaClass.getSimpleName() == "LazyJavaClassTypeConstructor" -> {
val javaClass = o.field<Any>("this\$0").field<JavaClassImpl>("jClass")
javaClass.getPsi().getName()!!.appendQuoted()
}
o.javaClass.getSimpleName() == "DeserializedType" -> {
val typeDeserializer = o.field<TypeDeserializer>("typeDeserializer")
val context = typeDeserializer.field<DeserializationContext>("c")
val typeProto = o.field<ProtoBuf.Type>("typeProto")
val text = when {
typeProto.hasClassName() -> context.nameResolver.getClassId(typeProto.className).asSingleFqName().asString()
typeProto.hasTypeParameter() -> {
val classifier = (o as KotlinType).constructor.declarationDescriptor!!
"" + classifier.name + " in " + DescriptorUtils.getFqName(classifier.containingDeclaration)
}
else -> "???"
}
text.appendQuoted()
}
o is JavaNamedElement -> {
o.getName().appendQuoted()
}
o is JavaTypeImpl<*> -> {
o.getPsi().getPresentableText().appendQuoted()
}
o is Collection<*> -> {
if (o.isEmpty()) {
sb.append("[empty]")
}
else {
sb.append("[${o.size}] ")
o.joinTo(sb, ", ", prefix = "{", postfix = "}", limit = 3) { render(it) }
}
}
o is KotlinTypeImpl -> {
StringBuilder {
append(o.getConstructor())
if (!o.getArguments().isEmpty()) {
append("<${o.getArguments().size()}>")
}
}.appendQuoted()
}
o is ResolutionCandidate<*> -> DescriptorRenderer.COMPACT.render(o.getDescriptor()).appendQuoted()
o is ResolutionTaskHolder<*, *> -> o.field<BasicCallResolutionContext>("basicCallResolutionContext").call.getCallElement().getDebugText()?.appendQuoted()
}
return sb.toString()
}
}
private fun <T> Any.field(name: String): T {
val field = this.javaClass.getDeclaredField(name)
field.setAccessible(true)
@Suppress("UNCHECKED_CAST")
return field.get(this) as T
}
private fun Printer.indent(body: Printer.() -> Unit): Printer {
pushIndent()
body()
popIndent()
return this
}
private fun GenericDeclaration?.getDeclarationName(): String? {
return when (this) {
is Class<*> -> getName().substringAfterLast(".")
is Method -> getDeclaringClass().getDeclarationName() + "::" + getName() + "()"
is Constructor<*> -> getDeclaringClass().getDeclarationName() + "::" + getName() + "()"
else -> "<no name>"
}
}