From c21693b0d061c1ee2e218701706dd7c180501d4f Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Fri, 15 Apr 2016 16:14:00 +0300 Subject: [PATCH] Serialize KotlinAnnotationProvider --- .../annotation/AnnotatedElementDescriptor.kt | 17 +++++++ .../kotlin/annotation/AnnotationWriter.kt | 28 +++++++++++ .../annotation/CompactAnnotationWriter.kt | 50 +++++++++++++++++++ .../kotlin/annotation/CompactNotationType.kt | 12 +++++ .../annotation/KotlinAnnotationProvider.kt | 42 ++++++++-------- .../annotation/AnnotationListParseTest.kt | 18 +++++-- .../annotation/AnnotationSerializationTest.kt | 43 ++++++++++++++++ 7 files changed, 185 insertions(+), 25 deletions(-) create mode 100644 libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotationWriter.kt create mode 100644 libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/CompactAnnotationWriter.kt create mode 100644 libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/CompactNotationType.kt create mode 100644 libraries/tools/kotlin-annotation-processing/src/test/kotlin/org/jetbrains/kotlin/annotation/AnnotationSerializationTest.kt diff --git a/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotatedElementDescriptor.kt b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotatedElementDescriptor.kt index c30b55ed6d4..0857dfe8c42 100644 --- a/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotatedElementDescriptor.kt +++ b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotatedElementDescriptor.kt @@ -23,4 +23,21 @@ sealed class AnnotatedElementDescriptor(val classFqName: String) { override fun hashCode() = 31 * classFqName.hashCode() + fieldName.hashCode() } +} + +fun AnnotationWriter.writeAnnotatedElement(annotation: String, element: AnnotatedElementDescriptor) { + when (element) { + is AnnotatedElementDescriptor.Class -> { + writeAnnotatedClass(annotation, element.classFqName) + } + is AnnotatedElementDescriptor.Constructor -> { + writeAnnotatedMethod(annotation, element.classFqName, AnnotatedElementDescriptor.Constructor.METHOD_NAME) + } + is AnnotatedElementDescriptor.Method -> { + writeAnnotatedMethod(annotation, element.classFqName, element.methodName) + } + is AnnotatedElementDescriptor.Field -> { + writeAnnotatedField(annotation, element.classFqName, element.fieldName) + } + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotationWriter.kt b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotationWriter.kt new file mode 100644 index 00000000000..3931676fa03 --- /dev/null +++ b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/AnnotationWriter.kt @@ -0,0 +1,28 @@ +package org.jetbrains.kotlin.annotation + +import java.io.Writer +import org.jetbrains.kotlin.annotation.CompactNotationType as Notation + +abstract class AnnotationWriter(private val writer: Writer) { + companion object { + private val LINE_SEP = System.getProperty("line.separator") + } + + abstract fun writeClassDeclaration(classFqName: String) + abstract fun writeAnnotatedClass(annotationName: String, classFqName: String) + abstract fun writeAnnotatedMethod(annotationName: String, classFqName: String, methodName: String) + abstract fun writeAnnotatedField(annotationName: String, classFqName: String, fieldName: String) + + protected fun writeLine(vararg parts: String?) { + var i = 0 + for (part in parts) { + if (part == null) continue + if (i > 0) { + writer.write(" ") + } + writer.write(part) + i++ + } + writer.write(LINE_SEP) + } +} diff --git a/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/CompactAnnotationWriter.kt b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/CompactAnnotationWriter.kt new file mode 100644 index 00000000000..d8e18002b91 --- /dev/null +++ b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/CompactAnnotationWriter.kt @@ -0,0 +1,50 @@ +package org.jetbrains.kotlin.annotation + +import java.io.Writer +import org.jetbrains.kotlin.annotation.CompactNotationType as Notation + +class CompactAnnotationWriter(writer: Writer) : AnnotationWriter(writer) { + private val annotationsShortener = NameShortener(Notation.SHORTENED_ANNOTATION) + private val packageShortener = NameShortener(Notation.SHORTENED_PACKAGE_NAME) + + override fun writeClassDeclaration(classFqName: String) { + writeLine(Notation.CLASS_DECLARATION, getShortenedClassName(classFqName)) + } + + override fun writeAnnotatedClass(annotationName: String, classFqName: String) { + writeAnnotated(Notation.ANNOTATED_CLASS, annotationName, classFqName) + } + + override fun writeAnnotatedMethod(annotationName: String, classFqName: String, methodName: String) { + writeAnnotated(Notation.ANNOTATED_METHOD, annotationName, classFqName, methodName) + } + + override fun writeAnnotatedField(annotationName: String, classFqName: String, fieldName: String) { + writeAnnotated(Notation.ANNOTATED_FIELD, annotationName, classFqName, fieldName) + } + + private fun writeAnnotated(type: String, annotationName: String, className: String, memberName: String? = null) { + writeLine(type, annotationsShortener[annotationName].toString(), getShortenedClassName(className), memberName) + } + + private fun getShortenedClassName(fqName: String): String { + val lastDotIndex = fqName.lastIndexOf('.') + if (lastDotIndex == -1) return fqName + + val packageName = fqName.substring(0, lastDotIndex) + val simpleName = fqName.substring(lastDotIndex + 1) + + return "${packageShortener[packageName]}/$simpleName" + } + + private inner class NameShortener(private val type: String) { + private val names = hashMapOf() + + operator fun get(name: String): Int = + names.getOrPut(name) { + val id = names.size + writeLine(type, name, "$id") + id + } + } +} diff --git a/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/CompactNotationType.kt b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/CompactNotationType.kt new file mode 100644 index 00000000000..23ce2facafa --- /dev/null +++ b/libraries/tools/kotlin-annotation-processing/src/main/kotlin/org/jetbrains/kotlin/annotation/CompactNotationType.kt @@ -0,0 +1,12 @@ +package org.jetbrains.kotlin.annotation + +object CompactNotationType { + const val ANNOTATED_CLASS = "c" + const val ANNOTATED_METHOD = "m" + const val ANNOTATED_FIELD = "f" + + const val SHORTENED_ANNOTATION = "a" + const val SHORTENED_PACKAGE_NAME = "p" + + const val CLASS_DECLARATION = "d" +} \ 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 index 1ee857e7850..b4b46c722de 100644 --- 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 @@ -19,20 +19,10 @@ package org.jetbrains.kotlin.annotation import java.io.File import java.io.Reader import java.io.StringReader +import java.util.* +import org.jetbrains.kotlin.annotation.CompactNotationType as Notation class KotlinAnnotationProvider(annotationsReader: Reader) { - - private companion object { - const val ANNOTATED_CLASS = "c" - const val ANNOTATED_METHOD = "m" - const val ANNOTATED_FIELD = "f" - - const val SHORTENED_ANNOTATION = "a" - const val SHORTENED_PACKAGE_NAME = "p" - - const val CLASS_DECLARATION = "d" - } - constructor(annotationsFile: File) : this(annotationsFile.reader().buffered()) constructor() : this(StringReader("")) @@ -80,26 +70,26 @@ class KotlinAnnotationProvider(annotationsReader: Reader) { val type = lineParts[0] when (type) { - SHORTENED_ANNOTATION -> handleShortenedName(shortenedAnnotationCache, lineParts) - SHORTENED_PACKAGE_NAME -> handleShortenedName(shortenedPackageNameCache, lineParts) - CLASS_DECLARATION -> { + Notation.SHORTENED_ANNOTATION -> handleShortenedName(shortenedAnnotationCache, lineParts) + Notation.SHORTENED_PACKAGE_NAME -> handleShortenedName(shortenedPackageNameCache, lineParts) + Notation.CLASS_DECLARATION -> { val classFqName = expandClassName(lineParts[1]).replace('$', '.') kotlinClassesInternal.add(classFqName) } - ANNOTATED_CLASS, ANNOTATED_FIELD, ANNOTATED_METHOD -> { + Notation.ANNOTATED_CLASS, Notation.ANNOTATED_FIELD, Notation.ANNOTATED_METHOD -> { val annotationName = expandAnnotation(lineParts[1]) val classFqName = expandClassName(lineParts[2]).replace('$', '.') val elementName = if (lineParts.size == 4) lineParts[3] else null - val set = annotatedKotlinElementsInternal.getOrPut(annotationName) { hashSetOf() } + val set = annotatedKotlinElementsInternal.getOrPut(annotationName) { HashSet() } set.add(when (type) { - ANNOTATED_CLASS -> AnnotatedElementDescriptor.Class(classFqName) - ANNOTATED_FIELD -> { + Notation.ANNOTATED_CLASS -> AnnotatedElementDescriptor.Class(classFqName) + Notation.ANNOTATED_FIELD -> { val name = elementName ?: throw AssertionError("Name for field must be provided") AnnotatedElementDescriptor.Field(classFqName, name) } - ANNOTATED_METHOD -> { + Notation.ANNOTATED_METHOD -> { val name = elementName ?: throw AssertionError("Name for method must be provided") if (AnnotatedElementDescriptor.Constructor.METHOD_NAME == name) @@ -116,4 +106,16 @@ class KotlinAnnotationProvider(annotationsReader: Reader) { } } } + + fun writeAnnotations(writer: AnnotationWriter) { + for ((annotation, elements) in annotatedKotlinElements) { + for (element in elements) { + writer.writeAnnotatedElement(annotation, element) + } + } + + for (className in kotlinClasses) { + writer.writeClassDeclaration(className) + } + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-annotation-processing/src/test/kotlin/org/jetbrains/kotlin/annotation/AnnotationListParseTest.kt b/libraries/tools/kotlin-annotation-processing/src/test/kotlin/org/jetbrains/kotlin/annotation/AnnotationListParseTest.kt index 0d556423bf9..b1089c63be3 100644 --- a/libraries/tools/kotlin-annotation-processing/src/test/kotlin/org/jetbrains/kotlin/annotation/AnnotationListParseTest.kt +++ b/libraries/tools/kotlin-annotation-processing/src/test/kotlin/org/jetbrains/kotlin/annotation/AnnotationListParseTest.kt @@ -1,12 +1,16 @@ package org.jetbrains.kotlin.annotation import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Test import java.io.File -import org.junit.Assert.* -import java.io.IOException -public class AnnotationListParseTest { +open class AnnotationListParseTest { + companion object { + const val ANNOTATIONS_FILE_NAME = "annotations.txt" + const val PARSED_FILE_NAME = "parsed.txt" + } @Test fun testAnnotatedGettersSetters() = doTest("annotatedGettersSetters") @@ -48,8 +52,12 @@ public class AnnotationListParseTest { private val resourcesRootFile = File("src/test/resources/parse") private fun doTest(testName: String) { - val annotationsFile = File(resourcesRootFile, "$testName/annotations.txt") - val expectedFile = File(resourcesRootFile, "$testName/parsed.txt") + doTest(testDir = File(resourcesRootFile, testName)) + } + + protected open fun doTest(testDir: File) { + val annotationsFile = File(testDir, ANNOTATIONS_FILE_NAME) + val expectedFile = File(testDir, PARSED_FILE_NAME) assertTrue(annotationsFile.absolutePath + " does not exist.", annotationsFile.exists()) diff --git a/libraries/tools/kotlin-annotation-processing/src/test/kotlin/org/jetbrains/kotlin/annotation/AnnotationSerializationTest.kt b/libraries/tools/kotlin-annotation-processing/src/test/kotlin/org/jetbrains/kotlin/annotation/AnnotationSerializationTest.kt new file mode 100644 index 00000000000..7541d639104 --- /dev/null +++ b/libraries/tools/kotlin-annotation-processing/src/test/kotlin/org/jetbrains/kotlin/annotation/AnnotationSerializationTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2016 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.google.common.io.Files +import java.io.File + +class AnnotationSerializationTest : AnnotationListParseTest() { + // after reading and serializing annotations file should not change the result of AnnotationListParseTest + override fun doTest(testDir: File) { + val workingDir = Files.createTempDir() + testDir.copyRecursively(workingDir) + + val annotationsFile = File(workingDir, ANNOTATIONS_FILE_NAME) + val annotationProvider = KotlinAnnotationProvider(annotationsFile) + annotationsFile.delete() + + val writer = annotationsFile.bufferedWriter() + try { + val annotationWriter = CompactAnnotationWriter(writer) + annotationProvider.writeAnnotations(annotationWriter) + } + finally { + writer.close() + } + + super.doTest(workingDir) + } +} \ No newline at end of file