Annotation collector compiler plugin
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="plugin-api" />
|
||||
<orderEntry type="module" module-name="backend" />
|
||||
<orderEntry type="module" module-name="frontend" />
|
||||
<orderEntry type="library" name="kotlin-runtime" level="project" />
|
||||
<orderEntry type="module" module-name="frontend.java" />
|
||||
</component>
|
||||
</module>
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.annotation
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
|
||||
import org.jetbrains.kotlin.codegen.DelegatingClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.StringWriter
|
||||
import java.io.Writer
|
||||
import java.util.regex.Pattern
|
||||
import java.util.regex.PatternSyntaxException
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
public abstract class AnnotationCollectorExtensionBase() : ClassBuilderInterceptorExtension {
|
||||
|
||||
private object RecordTypes {
|
||||
val ANNOTATED_CLASS = "c"
|
||||
val ANNOTATED_METHOD = "m"
|
||||
val ANNOTATED_FIELD = "f"
|
||||
|
||||
val SHORTENED_ANNOTATION = "a"
|
||||
val SHORTENED_PACKAGE_NAME = "p"
|
||||
}
|
||||
|
||||
protected abstract val annotationFilterList: List<String>?
|
||||
|
||||
private val classBuilderFactories = arrayListOf<AnnotationCollectorClassBuilderFactory>()
|
||||
|
||||
private val shortenedAnnotationCache = ShortenedNameCache(RecordTypes.SHORTENED_ANNOTATION)
|
||||
private val shortenedPackageNameCache = ShortenedNameCache(RecordTypes.SHORTENED_PACKAGE_NAME)
|
||||
|
||||
override fun interceptClassBuilderFactory(
|
||||
interceptedFactory: ClassBuilderFactory,
|
||||
bindingContext: BindingContext,
|
||||
diagnostics: DiagnosticSink
|
||||
): ClassBuilderFactory {
|
||||
val factory = AnnotationCollectorClassBuilderFactory(interceptedFactory, getWriter(diagnostics), diagnostics)
|
||||
classBuilderFactories.add(factory)
|
||||
return factory
|
||||
}
|
||||
|
||||
protected abstract fun getWriter(diagnostic: DiagnosticSink): Writer
|
||||
|
||||
private inner class AnnotationCollectorClassBuilderFactory(
|
||||
private val delegateFactory: ClassBuilderFactory,
|
||||
val writer: Writer,
|
||||
val diagnostics: DiagnosticSink
|
||||
) : ClassBuilderFactory {
|
||||
|
||||
override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder {
|
||||
return AnnotationCollectorClassBuilder(delegateFactory.newClassBuilder(origin), writer, diagnostics)
|
||||
}
|
||||
|
||||
override fun getClassBuilderMode() = delegateFactory.getClassBuilderMode()
|
||||
|
||||
override fun asText(builder: ClassBuilder?): String? {
|
||||
return delegateFactory.asText((builder as AnnotationCollectorClassBuilder).delegateClassBuilder)
|
||||
}
|
||||
|
||||
override fun asBytes(builder: ClassBuilder?): ByteArray? {
|
||||
return delegateFactory.asBytes((builder as AnnotationCollectorClassBuilder).delegateClassBuilder)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
for (factory in classBuilderFactories) {
|
||||
factory.writer.close()
|
||||
}
|
||||
classBuilderFactories.clear()
|
||||
|
||||
delegateFactory.close()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class AnnotationCollectorClassBuilder(
|
||||
internal val delegateClassBuilder: ClassBuilder,
|
||||
val writer: Writer,
|
||||
val diagnostics: DiagnosticSink
|
||||
) : DelegatingClassBuilder() {
|
||||
private val annotationFilterEnabled: Boolean
|
||||
private val annotationFilters: List<Pattern>
|
||||
|
||||
init {
|
||||
val nullableAnnotations = annotationFilterList?.map { it.compilePatternOpt() } ?: listOf()
|
||||
annotationFilterEnabled = nullableAnnotations.isNotEmpty()
|
||||
annotationFilters = nullableAnnotations.filterNotNull()
|
||||
}
|
||||
|
||||
private val classVisitor: ClassVisitor by Delegates.lazy {
|
||||
object : ClassVisitor(Opcodes.ASM5, super.getVisitor()) {
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
recordAnnotation(null, RecordTypes.ANNOTATED_CLASS, desc)
|
||||
return super.visitAnnotation(desc, visible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var currentClassSimpleName: String? = null
|
||||
|
||||
private var currentPackageName: String? = null
|
||||
|
||||
override fun getVisitor() = classVisitor
|
||||
|
||||
override fun getDelegate() = delegateClassBuilder
|
||||
|
||||
override fun defineClass(
|
||||
origin: PsiElement?,
|
||||
version: Int,
|
||||
access: Int,
|
||||
name: String,
|
||||
signature: String?,
|
||||
superName: String,
|
||||
interfaces: Array<out String>
|
||||
) {
|
||||
currentClassSimpleName = name.substringAfterLast('/')
|
||||
currentPackageName = name.substringBeforeLast('/', "").replace('/', '.')
|
||||
|
||||
super.defineClass(origin, version, access, name, signature, superName, interfaces)
|
||||
}
|
||||
|
||||
override fun newField(
|
||||
origin: JvmDeclarationOrigin,
|
||||
access: Int,
|
||||
name: String,
|
||||
desc: String,
|
||||
signature: String?,
|
||||
value: Any?
|
||||
): FieldVisitor {
|
||||
return object : FieldVisitor(Opcodes.ASM5, super.newField(origin, access, name, desc, signature, value)) {
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
recordAnnotation(name, RecordTypes.ANNOTATED_FIELD, desc)
|
||||
return super.visitAnnotation(desc, visible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun newMethod(
|
||||
origin: JvmDeclarationOrigin,
|
||||
access: Int,
|
||||
name: String,
|
||||
desc: String,
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?
|
||||
): MethodVisitor {
|
||||
return object : MethodVisitor(Opcodes.ASM5, super.newMethod(origin, access, name, desc, signature, exceptions)) {
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
recordAnnotation(name, RecordTypes.ANNOTATED_METHOD, desc)
|
||||
return super.visitAnnotation(desc, visible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAnnotationHandled(annotationFqName: String): Boolean {
|
||||
return if (annotationFilterEnabled)
|
||||
annotationFilters.any { it.matcher(annotationFqName).matches() }
|
||||
else !annotationFqName.startsWith("kotlin.jvm.internal.") //apply to all
|
||||
}
|
||||
|
||||
private fun recordAnnotation(name: String?, type: String, annotationDesc: String) {
|
||||
val annotationFqName = Type.getType(annotationDesc).getClassName()
|
||||
if (!isAnnotationHandled(annotationFqName)) return
|
||||
|
||||
try {
|
||||
val annotationId = shortenedAnnotationCache.save(annotationFqName, writer)
|
||||
val packageName = this.currentPackageName!!
|
||||
|
||||
val packageNameId = if (!packageName.isEmpty())
|
||||
shortenedPackageNameCache.save(packageName, writer)
|
||||
else null
|
||||
|
||||
val className = this.currentClassSimpleName!!
|
||||
val outputClassName = if (packageNameId == null) className else "$packageNameId/$className"
|
||||
val elementName = if (name != null) " $name" else ""
|
||||
|
||||
writer.write("$type $annotationId $outputClassName$elementName\n")
|
||||
}
|
||||
catch (e: IOException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.compilePatternOpt(): Pattern? {
|
||||
return try {
|
||||
Pattern.compile(this)
|
||||
}
|
||||
catch (e: PatternSyntaxException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ShortenedNameCache(val type: String) {
|
||||
private val internalCache = hashMapOf<String, String>()
|
||||
private var counter: Int = 0
|
||||
|
||||
fun save(name: String, writer: Writer): String {
|
||||
return internalCache.getOrPut(name) {
|
||||
val resultId = counter.toString()
|
||||
writer.write("$type $name $resultId\n")
|
||||
counter += 1
|
||||
resultId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AnnotationCollectorExtension(
|
||||
override val annotationFilterList: List<String>? = null,
|
||||
val outputFilename: String? = null
|
||||
) : AnnotationCollectorExtensionBase() {
|
||||
|
||||
override fun getWriter(diagnostic: DiagnosticSink): Writer {
|
||||
try {
|
||||
return with (File(outputFilename)) {
|
||||
val parent = getParentFile()
|
||||
if (!parent.exists()) parent.mkdirs()
|
||||
bufferedWriter()
|
||||
}
|
||||
}
|
||||
catch (e: IOException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.annotation
|
||||
|
||||
import com.intellij.mock.MockProject
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
|
||||
import org.jetbrains.kotlin.compiler.plugin.CliOption
|
||||
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
|
||||
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
|
||||
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.CompilerConfigurationKey
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.MemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters1
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.JetCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.JetDeclaration
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Writer
|
||||
import java.util.regex.Pattern
|
||||
import java.util.regex.PatternSyntaxException
|
||||
|
||||
public object AnnotationCollectorConfigurationKeys {
|
||||
public val ANNOTATION_FILTER_LIST: CompilerConfigurationKey<List<String>> =
|
||||
CompilerConfigurationKey.create<List<String>>("annotation filter regular expressions")
|
||||
public val OUTPUT_FILENAME: CompilerConfigurationKey<String> =
|
||||
CompilerConfigurationKey.create<String>("output file")
|
||||
}
|
||||
|
||||
public class AnnotationCollectorCommandLineProcessor : CommandLineProcessor {
|
||||
companion object {
|
||||
public val ANNOTATION_COLLECTOR_COMPILER_PLUGIN_ID: String = "org.jetbrains.kotlin.kapt"
|
||||
|
||||
public val ANNOTATION_FILTER_LIST_OPTION: CliOption =
|
||||
CliOption("annotations", "<path>", "Annotation filter regular expressions, separated by commas", required = false)
|
||||
|
||||
public val OUTPUT_FILENAME_OPTION: CliOption =
|
||||
CliOption("output", "<path>", "File in which annotated declarations will be placed", required = false)
|
||||
}
|
||||
|
||||
override val pluginId: String = ANNOTATION_COLLECTOR_COMPILER_PLUGIN_ID
|
||||
|
||||
override val pluginOptions: Collection<CliOption> = listOf(ANNOTATION_FILTER_LIST_OPTION, OUTPUT_FILENAME_OPTION)
|
||||
|
||||
override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) {
|
||||
when (option) {
|
||||
ANNOTATION_FILTER_LIST_OPTION -> {
|
||||
val annotations = value.split(',').filter { !it.isEmpty() }.toList()
|
||||
configuration.put(AnnotationCollectorConfigurationKeys.ANNOTATION_FILTER_LIST, annotations)
|
||||
}
|
||||
OUTPUT_FILENAME_OPTION -> configuration.put(AnnotationCollectorConfigurationKeys.OUTPUT_FILENAME, value)
|
||||
else -> throw CliOptionProcessingException("Unknown option: ${option.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AnnotationCollectorComponentRegistrar : ComponentRegistrar {
|
||||
public override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
|
||||
val annotationFilterList = configuration.get(AnnotationCollectorConfigurationKeys.ANNOTATION_FILTER_LIST)
|
||||
val outputFilename = configuration.get(AnnotationCollectorConfigurationKeys.OUTPUT_FILENAME)
|
||||
|
||||
if (outputFilename != null) {
|
||||
val collectorExtension = AnnotationCollectorExtension(annotationFilterList, outputFilename)
|
||||
ClassBuilderInterceptorExtension.registerExtension(project, collectorExtension)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user