Add annotation processor wrapper as a Maven artifact

This commit is contained in:
Yan Zhulanow
2015-05-15 18:07:41 +03:00
parent c6e11949b8
commit 964e57a8f8
5 changed files with 348 additions and 0 deletions
+1
View File
@@ -89,6 +89,7 @@
<!--because it is used in tests but cannot be added as test-dependency-->
<!--(kotlin-gradle-plugin module will be recognized as kotlin-js module)-->
<module>tools/kotlin-js-library</module>
<module>tools/kotlin-annotation-processing</module>
<module>tools/kotlin-gradle-plugin</module>
<module>tools/kotlin-gradle-plugin-core</module>
<module>tools/kotlin-gradle-plugin-api</module>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<properties>
<maven.version>3.0.4</maven.version>
</properties>
<parent>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-project</artifactId>
<version>0.1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlin-annotation-processing</artifactId>
<packaging>jar</packaging>
<description>Annotation Processor wrapper for Kotlin</description>
<repositories>
<repository>
<id>jetbrains-utils</id>
<url>http://repository.jetbrains.com/utils</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>gradle-api</artifactId>
<version>1.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.android.tools.build</groupId>
<artifactId>gradle</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${project.version}</version>
<configuration>
<annotationPaths>
<annotationPath>${basedir}/kotlinAnnotation</annotationPath>
</annotationPaths>
</configuration>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals> <goal>compile</goal> </goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals> <goal>test-compile</goal> </goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,106 @@
/*
* 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 javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.AnnotationMirror
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic
import javax.tools.StandardLocation
import kotlin.properties.Delegates
public class AnnotationProcessorStub : AbstractProcessor() {
override fun process(annotations: Set<TypeElement>?, roundEnv: RoundEnvironment?) = true
}
abstract class AnnotatedElementDescriptor(public val classFqName: String)
data class AnnotatedClassDescriptor(classFqName: String) : AnnotatedElementDescriptor(classFqName)
data class AnnotatedMethodDescriptor(classFqName: String, public val methodName: String) : AnnotatedElementDescriptor(classFqName)
data class AnnotatedFieldDescriptor(classFqName: String, public val fieldName: String) : AnnotatedElementDescriptor(classFqName)
public abstract class AnnotationProcessorWrapper(private val processorFqName: String) : Processor {
private val processor: Processor by Delegates.lazy {
try {
val instance = Class.forName(processorFqName).newInstance() as? Processor
instance ?: throw IllegalArgumentException("Instance has a wrong type")
}
catch (e: Exception) {
AnnotationProcessorStub()
}
}
private var processingEnv: ProcessingEnvironment by Delegates.notNull()
private var kotlinAnnotationsProvider: KotlinAnnotationProvider by Delegates.notNull()
private var roundCounter = 0
override fun getCompletions(
element: Element?,
annotation: AnnotationMirror?,
member: ExecutableElement?,
userText: String?
): MutableIterable<Completion> {
return processor.getCompletions(element, annotation, member, userText)
}
override fun init(processingEnv: ProcessingEnvironment) {
this.processingEnv = processingEnv
if (processor is AnnotationProcessorStub) {
processingEnv.err("Can't instantiate processor $processorFqName")
return
}
val annotationsTxt = processingEnv.getFiler().getResource(StandardLocation.CLASS_PATH, "", "0apt/annotations.txt")
kotlinAnnotationsProvider = FileObjectKotlinAnnotationProvider(annotationsTxt)
processor.init(processingEnv)
}
override fun getSupportedAnnotationTypes(): MutableSet<String> {
val supportedAnnotations = processor.getSupportedAnnotationTypes().toMutableSet()
supportedAnnotations.add("__gen.KotlinAptAnnotation")
return supportedAnnotations
}
override fun getSupportedSourceVersion(): SourceVersion? {
return processor.getSupportedSourceVersion()
}
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment): Boolean {
roundCounter = roundCounter + 1
val annotatedKotlinElements = kotlinAnnotationsProvider.annotatedKotlinElements
val roundEnvironmentWrapper = RoundEnvironmentWrapper(processingEnv, roundEnv, roundCounter, annotatedKotlinElements)
return processor.process(annotations, roundEnvironmentWrapper)
}
override fun getSupportedOptions(): MutableSet<String> {
return processor.getSupportedOptions()
}
private fun ProcessingEnvironment.err(message: String) {
getMessager().printMessage(Diagnostic.Kind.ERROR, message)
}
}
@@ -0,0 +1,97 @@
/*
* 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 javax.tools.FileObject
import kotlin.properties.Delegates
public abstract class KotlinAnnotationProvider {
private companion object {
val ANNOTATED_CLASS = "c"
val ANNOTATED_METHOD = "m"
val ANNOTATED_FIELD = "f"
val SHORTENED_ANNOTATION = "a"
val SHORTENED_PACKAGE_NAME = "p"
}
public val annotatedKotlinElements: MutableMap<String, MutableSet<AnnotatedElementDescriptor>> by Delegates.lazy {
readAnnotations()
}
protected abstract val serializedAnnotations: String
private fun readAnnotations(): MutableMap<String, MutableSet<AnnotatedElementDescriptor>> {
val shortenedAnnotationCache = hashMapOf<String, String>()
val shortenedPackageNameCache = hashMapOf<String, String>()
fun expandAnnotation(s: String) = shortenedAnnotationCache.getOrElse(s) { s }
fun expandClassName(s: String): String {
val id = s.substringBefore('/', "")
if (id.isEmpty()) return s
val shortenedValue = shortenedPackageNameCache.get(id) ?:
throw RuntimeException("Value for $id couldn't be found in shrink cache")
return shortenedValue + '.' + s.substring(id.length() + 1)
}
val annotatedKotlinElements: MutableMap<String, MutableSet<AnnotatedElementDescriptor>> = hashMapOf()
for (line in serializedAnnotations.split('\n')) {
if (line.isEmpty()) continue
val lineParts = line.split(' ')
val type = lineParts[0]
when (type) {
SHORTENED_ANNOTATION -> handleShortenedName(shortenedAnnotationCache, lineParts)
SHORTENED_PACKAGE_NAME -> handleShortenedName(shortenedPackageNameCache, lineParts)
ANNOTATED_CLASS, ANNOTATED_FIELD, ANNOTATED_METHOD -> {
val annotationName = expandAnnotation(lineParts[1])
val classFqName = expandClassName(lineParts[2])
val elementName = if (lineParts.size() == 4) lineParts[3] else null
val set = annotatedKotlinElements.getOrPut(annotationName) { hashSetOf() }
set.add(when (type) {
ANNOTATED_CLASS -> AnnotatedClassDescriptor(classFqName)
ANNOTATED_FIELD -> AnnotatedFieldDescriptor(classFqName, elementName!!)
ANNOTATED_METHOD -> AnnotatedMethodDescriptor(classFqName, elementName!!)
else -> throw AssertionError("Should not occur")
})
}
else -> throw RuntimeException("Unknown type: $type")
}
}
return annotatedKotlinElements
}
private fun handleShortenedName(cache: MutableMap<String, String>, lineParts: List<String>) {
val name = lineParts[1]
val id = lineParts[2]
cache.put(id, name)
}
}
public class FileObjectKotlinAnnotationProvider(val annotationsFileObject: FileObject): KotlinAnnotationProvider() {
override val serializedAnnotations: String
get() = annotationsFileObject.getCharContent(false).toString()
}
@@ -0,0 +1,64 @@
package org.jetbrains.kotlin.annotation
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
private class RoundEnvironmentWrapper(
val processingEnv: ProcessingEnvironment,
val parent: RoundEnvironment,
val roundNumber: Int,
val annotatedElementDescriptors: Map<String, Set<AnnotatedElementDescriptor>>
) : RoundEnvironment {
override fun getRootElements(): MutableSet<out Element>? {
return parent.getRootElements()
}
override fun getElementsAnnotatedWith(a: TypeElement): MutableSet<out Element>? {
val elements = parent.getElementsAnnotatedWith(a).toHashSet()
elements.addAll(resolveKotlinElements(a.getQualifiedName().toString()))
return elements
}
override fun getElementsAnnotatedWith(a: Class<out Annotation>): MutableSet<out Element>? {
val elements = parent.getElementsAnnotatedWith(a).toHashSet()
elements.addAll(resolveKotlinElements(a.getName()))
return elements
}
override fun processingOver() = parent.processingOver()
override fun errorRaised() = parent.errorRaised()
private fun TypeElement.filterEnclosedElements(kind: ElementKind, name: String): List<Element> {
return getEnclosedElements().filter { it.getKind() == kind && it.getSimpleName().toString() == name }
}
private fun Element.hasAnnotation(annotationFqName: String): Boolean {
return getAnnotationMirrors().any { annotationFqName == it.getAnnotationType().asElement().toString() }
}
private fun resolveKotlinElements(annotationFqName: String): Set<Element> {
if (roundNumber > 1) return setOf()
val descriptors = annotatedElementDescriptors.get(annotationFqName) ?: setOf()
return descriptors.fold(hashSetOf<Element>()) { set, descriptor ->
val clazz = processingEnv.getElementUtils().getTypeElement(descriptor.classFqName) ?: return@fold set
when (descriptor) {
is AnnotatedClassDescriptor -> set.add(clazz)
is AnnotatedFieldDescriptor -> {
set.addAll(clazz.filterEnclosedElements(ElementKind.FIELD, descriptor.fieldName)
.filter { it.hasAnnotation(annotationFqName) })
}
is AnnotatedMethodDescriptor -> {
set.addAll(clazz.filterEnclosedElements(ElementKind.METHOD, descriptor.methodName)
.filter { it.hasAnnotation(annotationFqName) })
}
}
set
}
}
}