From 964e57a8f82ecc48b0e501c4bd4243bb5b541bef Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Fri, 15 May 2015 18:07:41 +0300 Subject: [PATCH] Add annotation processor wrapper as a Maven artifact --- libraries/pom.xml | 1 + .../kotlin-annotation-processing/pom.xml | 80 +++++++++++++ .../annotation/AnnotationProcessorWrapper.kt | 106 ++++++++++++++++++ .../annotation/KotlinAnnotationProvider.kt | 97 ++++++++++++++++ .../annotation/RoundEnvironmentWrapper.kt | 64 +++++++++++ 5 files changed, 348 insertions(+) create mode 100644 libraries/tools/kotlin-annotation-processing/pom.xml create mode 100644 libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotationProcessorWrapper.kt create mode 100644 libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/KotlinAnnotationProvider.kt create mode 100644 libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/RoundEnvironmentWrapper.kt diff --git a/libraries/pom.xml b/libraries/pom.xml index a10a511670e..63ae3880eec 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -89,6 +89,7 @@ tools/kotlin-js-library + tools/kotlin-annotation-processing tools/kotlin-gradle-plugin tools/kotlin-gradle-plugin-core tools/kotlin-gradle-plugin-api diff --git a/libraries/tools/kotlin-annotation-processing/pom.xml b/libraries/tools/kotlin-annotation-processing/pom.xml new file mode 100644 index 00000000000..dc04ac48d4e --- /dev/null +++ b/libraries/tools/kotlin-annotation-processing/pom.xml @@ -0,0 +1,80 @@ + + + + 4.0.0 + + 3.0.4 + + + + org.jetbrains.kotlin + kotlin-project + 0.1-SNAPSHOT + ../../pom.xml + + + kotlin-annotation-processing + jar + + Annotation Processor wrapper for Kotlin + + + + jetbrains-utils + http://repository.jetbrains.com/utils + + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${project.version} + + + org.jetbrains.kotlin + gradle-api + 1.6 + provided + + + com.android.tools.build + gradle + 1.1.0 + provided + + + + + ${project.basedir}/src/main/kotlin + + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${project.version} + + + ${basedir}/kotlinAnnotation + + + + + + compile + compile + compile + + + + test-compile + test-compile + test-compile + + + + + + diff --git a/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotationProcessorWrapper.kt b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotationProcessorWrapper.kt new file mode 100644 index 00000000000..81534c0258a --- /dev/null +++ b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotationProcessorWrapper.kt @@ -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?, 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 { + 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 { + val supportedAnnotations = processor.getSupportedAnnotationTypes().toMutableSet() + supportedAnnotations.add("__gen.KotlinAptAnnotation") + return supportedAnnotations + } + + override fun getSupportedSourceVersion(): SourceVersion? { + return processor.getSupportedSourceVersion() + } + + override fun process(annotations: MutableSet?, 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 { + return processor.getSupportedOptions() + } + + private fun ProcessingEnvironment.err(message: String) { + getMessager().printMessage(Diagnostic.Kind.ERROR, message) + } + +} \ No newline at end of file diff --git a/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/KotlinAnnotationProvider.kt b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/KotlinAnnotationProvider.kt new file mode 100644 index 00000000000..7a3ce1d63c8 --- /dev/null +++ b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/KotlinAnnotationProvider.kt @@ -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> by Delegates.lazy { + readAnnotations() + } + + protected abstract val serializedAnnotations: String + + private fun readAnnotations(): MutableMap> { + val shortenedAnnotationCache = hashMapOf() + val shortenedPackageNameCache = hashMapOf() + + 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> = 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, lineParts: List) { + 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() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/RoundEnvironmentWrapper.kt b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/RoundEnvironmentWrapper.kt new file mode 100644 index 00000000000..0e7daec152c --- /dev/null +++ b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/RoundEnvironmentWrapper.kt @@ -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> +) : RoundEnvironment { + + override fun getRootElements(): MutableSet? { + return parent.getRootElements() + } + + override fun getElementsAnnotatedWith(a: TypeElement): MutableSet? { + val elements = parent.getElementsAnnotatedWith(a).toHashSet() + elements.addAll(resolveKotlinElements(a.getQualifiedName().toString())) + return elements + } + + override fun getElementsAnnotatedWith(a: Class): MutableSet? { + 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 { + 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 { + if (roundNumber > 1) return setOf() + + val descriptors = annotatedElementDescriptors.get(annotationFqName) ?: setOf() + return descriptors.fold(hashSetOf()) { 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 + } + } +}