Support package level annotations in Java

#KT-10942 In Progress
This commit is contained in:
Denis Zharkov
2017-07-02 15:17:46 +03:00
parent b7ead75947
commit 79e14f88e4
15 changed files with 222 additions and 21 deletions
@@ -64,7 +64,7 @@ internal fun typeParameters(typeParameters: Array<PsiTypeParameter>): List<JavaT
internal fun classifierTypes(classTypes: Array<PsiClassType>): Collection<JavaClassifierType> =
classTypes.convert(::JavaClassifierTypeImpl)
internal fun annotations(annotations: Array<PsiAnnotation>): Collection<JavaAnnotation> =
internal fun annotations(annotations: Array<out PsiAnnotation>): Collection<JavaAnnotation> =
annotations.convert(::JavaAnnotationImpl)
internal fun namedAnnotationArguments(nameValuePairs: Array<PsiNameValuePair>): Collection<JavaAnnotationArgument> =
@@ -18,12 +18,13 @@ package org.jetbrains.kotlin.load.java.structure.impl
import com.intellij.psi.PsiPackage
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaPackage
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
class JavaPackageImpl(psiPackage: PsiPackage, private val scope: GlobalSearchScope) : JavaElementImpl<PsiPackage>(psiPackage), JavaPackage {
class JavaPackageImpl(
psiPackage: PsiPackage, private val scope: GlobalSearchScope
) : JavaElementImpl<PsiPackage>(psiPackage), JavaPackage, MapBasedJavaAnnotationOwner {
override fun getClasses(nameFilter: (Name) -> Boolean): Collection<JavaClass> {
val psiClasses = psi.getClasses(scope).filter {
@@ -38,4 +39,9 @@ class JavaPackageImpl(psiPackage: PsiPackage, private val scope: GlobalSearchSco
override val fqName: FqName
get() = FqName(psi.qualifiedName)
override val annotations: Collection<JavaAnnotation>
get() = org.jetbrains.kotlin.load.java.structure.impl.annotations(psi.annotationList?.annotations.orEmpty())
override val annotationsByFqName: Map<FqName?, JavaAnnotation> by buildLazyValueForMap()
}
@@ -43,10 +43,8 @@ import com.sun.tools.javac.util.Options
import org.jetbrains.kotlin.javac.wrappers.symbols.SymbolBasedClass
import org.jetbrains.kotlin.javac.wrappers.symbols.SymbolBasedClassifierType
import org.jetbrains.kotlin.javac.wrappers.symbols.SymbolBasedPackage
import org.jetbrains.kotlin.javac.wrappers.trees.TreeBasedClass
import org.jetbrains.kotlin.javac.wrappers.trees.TreeBasedPackage
import org.jetbrains.kotlin.javac.wrappers.trees.TreePathResolverCache
import org.jetbrains.kotlin.javac.wrappers.trees.computeClassId
import org.jetbrains.kotlin.javac.wrappers.trees.*
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaClassifier
import org.jetbrains.kotlin.load.java.structure.JavaPackage
@@ -142,6 +140,14 @@ class JavacWrapper(
}
.associateBy(TreeBasedPackage::fqName)
private val packageSourceAnnotations = compilationUnits
.filter {
it.sourceFile.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE) &&
it.packageName != null
}.associateBy({ FqName(it.packageName!!.toString()) }) { compilationUnit ->
compilationUnit.packageAnnotations.map { TreeBasedAnnotation(it, compilationUnit, this) }
}
private val kotlinClassifiersCache = KotlinClassifiersCache(if (javaFiles.isNotEmpty()) kotlinFiles else emptyList(), this)
private val treePathResolverCache = TreePathResolverCache(this)
private val symbolBasedClassesCache = hashMapOf<String, SymbolBasedClass?>()
@@ -212,6 +218,9 @@ class JavacWrapper(
.filterKeys { it.isSubpackageOf(fqName) && it != fqName }
.map { it.value }
fun getPackageAnnotationsFromSources(fqName: FqName): List<JavaAnnotation> =
packageSourceAnnotations[fqName] ?: emptyList()
fun findClassesFromPackage(fqName: FqName): List<JavaClass> =
javaClasses
.filterKeys { it?.parentOrNull() == fqName }
@@ -329,4 +338,4 @@ class JavacWrapper(
return symbol.let { SymbolBasedClass(it, this@JavacWrapper, it.classfile) }
}
}
}
@@ -17,7 +17,10 @@
package org.jetbrains.kotlin.javac.wrappers.symbols
import org.jetbrains.kotlin.javac.JavacWrapper
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaPackage
import org.jetbrains.kotlin.load.java.structure.MapBasedJavaAnnotationOwner
import org.jetbrains.kotlin.load.java.structure.buildLazyValueForMap
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import javax.lang.model.element.PackageElement
@@ -25,7 +28,7 @@ import javax.lang.model.element.PackageElement
class SymbolBasedPackage(
element: PackageElement,
javac: JavacWrapper
) : SymbolBasedElement<PackageElement>(element, javac), JavaPackage {
) : SymbolBasedElement<PackageElement>(element, javac), JavaPackage, MapBasedJavaAnnotationOwner {
override val fqName: FqName
get() = FqName(element.qualifiedName.toString())
@@ -33,9 +36,15 @@ class SymbolBasedPackage(
override val subPackages: Collection<JavaPackage>
get() = javac.findSubPackages(FqName(element.qualifiedName.toString()))
override val annotations: Collection<JavaAnnotation>
get() = element.annotationMirrors.map { SymbolBasedAnnotation(it, javac) }
override val annotationsByFqName: Map<FqName?, JavaAnnotation> by buildLazyValueForMap()
override fun getClasses(nameFilter: (Name) -> Boolean) =
javac.findClassesFromPackage(fqName).filter { nameFilter(it.name) }
override fun toString() = element.qualifiedName.toString()
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.javac.wrappers.trees
import com.sun.source.tree.CompilationUnitTree
import com.sun.source.util.TreePath
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.javac.JavacWrapper
@@ -27,11 +28,17 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
class TreeBasedAnnotation(
val annotation: JCTree.JCAnnotation,
val treePath: TreePath,
val javac: JavacWrapper
private val annotation: JCTree.JCAnnotation,
private val compilationUnit: CompilationUnitTree,
private val javac: JavacWrapper
) : JavaElement, JavaAnnotation {
constructor(
annotation: JCTree.JCAnnotation,
treePath: TreePath,
javac: JavacWrapper
) : this(annotation, treePath.compilationUnit, javac)
override val arguments: Collection<JavaAnnotationArgument>
get() = annotation.arguments.map { TreeBasedAnnotationArgument(Name.identifier(it.toString())) }
@@ -39,8 +46,8 @@ class TreeBasedAnnotation(
get() = resolve()?.computeClassId()
override fun resolve() =
javac.resolve(TreePath.getPath(treePath.compilationUnit, annotation.annotationType)) as? JavaClass
javac.resolve(TreePath.getPath(compilationUnit, annotation.annotationType)) as? JavaClass
}
class TreeBasedAnnotationArgument(override val name: Name) : JavaAnnotationArgument, JavaElement
class TreeBasedAnnotationArgument(override val name: Name) : JavaAnnotationArgument, JavaElement
@@ -154,4 +154,4 @@ class TreeBasedClass(
override fun findInnerClass(name: Name) = innerClasses[name]
}
}
@@ -18,12 +18,15 @@ package org.jetbrains.kotlin.javac.wrappers.trees
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.javac.JavacWrapper
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaPackage
import org.jetbrains.kotlin.load.java.structure.MapBasedJavaAnnotationOwner
import org.jetbrains.kotlin.load.java.structure.buildLazyValueForMap
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import javax.tools.JavaFileObject
class TreeBasedPackage(val name: String, val javac: JavacWrapper, val file: JavaFileObject) : JavaPackage {
class TreeBasedPackage(val name: String, val javac: JavacWrapper, val file: JavaFileObject) : JavaPackage, MapBasedJavaAnnotationOwner {
override val fqName: FqName
get() = FqName(name)
@@ -38,10 +41,15 @@ class TreeBasedPackage(val name: String, val javac: JavacWrapper, val file: Java
override fun getClasses(nameFilter: (Name) -> Boolean) =
javac.findClassesFromPackage(fqName).filter { nameFilter(it.fqName!!.shortName()) }
override val annotations
get() = javac.getPackageAnnotationsFromSources(fqName)
override val annotationsByFqName: Map<FqName?, JavaAnnotation> by buildLazyValueForMap()
override fun equals(other: Any?) = (other as? TreeBasedPackage)?.name == name
override fun hashCode() = name.hashCode()
override fun toString() = name
}
}
@@ -0,0 +1,5 @@
package test;
public class A {
}
@@ -0,0 +1,7 @@
package test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.PACKAGE)
public @interface Ann {
}
@@ -0,0 +1,2 @@
@Ann
package test;
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.preloading.ClassPreloadingUtils
import org.jetbrains.kotlin.preloading.Preloader
import org.jetbrains.kotlin.utils.PathUtil
import org.junit.Assert
import org.junit.Assert.assertEquals
import java.io.ByteArrayOutputStream
import java.io.File
@@ -45,12 +46,29 @@ object MockLibraryUtil {
sourcesPath: String,
jarName: String,
addSources: Boolean = false,
allowKotlinSources: Boolean = true,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList(),
useJava9: Boolean = false
): File {
return compileLibraryToJar(sourcesPath, KotlinTestUtils.tmpDir("testLibrary-" + jarName), jarName, addSources,
extraOptions, extraClasspath, useJava9)
return compileLibraryToJar(
sourcesPath, KotlinTestUtils.tmpDir("testLibrary-" + jarName), jarName, addSources,allowKotlinSources, extraOptions, extraClasspath
, useJava9)}
@JvmStatic
@JvmOverloads
fun compileJavaFilesLibraryToJar(
sourcesPath: String,
jarName: String,
addSources: Boolean = false,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList()
): File {
return compileJvmLibraryToJar(
sourcesPath, jarName, addSources,
allowKotlinSources = false,
extraClasspath = extraClasspath, extraOptions = extraOptions
)
}
@JvmStatic
@@ -60,6 +78,7 @@ object MockLibraryUtil {
contentDir: File,
jarName: String,
addSources: Boolean = false,
allowKotlinSources: Boolean = true,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList(),
useJava9: Boolean = false
@@ -69,6 +88,7 @@ object MockLibraryUtil {
val srcFile = File(sourcesPath)
val kotlinFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.kt"), srcFile)
if (srcFile.isFile || kotlinFiles.isNotEmpty()) {
Assert.assertTrue("Only java files are expected", allowKotlinSources)
compileKotlin(sourcesPath, classesDir, extraOptions, *extraClasspath.toTypedArray())
}
@@ -0,0 +1,112 @@
/*
* Copyright 2010-2017 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.jvm.compiler
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoots
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.MockLibraryUtil
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import java.io.File
class LoadJavaPackageAnnotationsTest : KtUsefulTestCase() {
companion object {
private val TEST_DATA_PATH = "compiler/testData/loadJavaPackageAnnotations/"
}
private fun doTest(useJavac: Boolean, configurator: (CompilerConfiguration) -> Unit) {
val environment =
KotlinCoreEnvironment.createForTests(
myTestRootDisposable,
KotlinTestUtils.newConfiguration(
ConfigurationKind.ALL, TestJdkKind.FULL_JDK, KotlinTestUtils.getAnnotationsJar()
).also {
if (useJavac) {
it.put(JVMConfigurationKeys.USE_JAVAC, true)
}
configurator(it)
},
EnvironmentConfigFiles.JVM_CONFIG_FILES
).apply {
if (useJavac) {
registerJavac()
}
}
val moduleDescriptor = JvmResolveUtil.analyze(
environment
).moduleDescriptor
val packageFragmentDescriptor =
moduleDescriptor.getPackage(FqName("test")).fragments
.singleOrNull {
it.getMemberScope().getContributedClassifier(Name.identifier("A"), NoLookupLocation.FROM_TEST) != null
}.let { assertInstanceOf(it, LazyJavaPackageFragment::class.java) }
val annotation = packageFragmentDescriptor.getPackageAnnotations().findAnnotation(FqName("test.Ann"))
assertNotNull(annotation)
val singleAnnotation = packageFragmentDescriptor.getPackageAnnotations().singleOrNull()
assertNotNull(singleAnnotation)
val annotationFqName = singleAnnotation!!.type.constructor.declarationDescriptor?.fqNameSafe
assertEquals(FqName("test.Ann"), annotationFqName)
}
fun testAnnotationFromSource() {
doTest(useJavac = false) {
it.addJavaSourceRoots(listOf(File(TEST_DATA_PATH)))
}
}
fun testAnnotationFromSourceWithJavac() {
doTest(useJavac = true) {
it.addJavaSourceRoots(listOf(File(TEST_DATA_PATH)))
}
}
fun testAnnotationFromCompiledCode() {
val jar = prepareJar()
doTest(useJavac = false) {
it.addJvmClasspathRoot(jar)
}
}
fun testAnnotationFromCompiledCodeWithJavac() {
val jar = prepareJar()
doTest(useJavac = true) {
it.addJvmClasspathRoot(jar)
}
}
private fun prepareJar() =
MockLibraryUtil.compileJavaFilesLibraryToJar(TEST_DATA_PATH, "result.jar")
}
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
import org.jetbrains.kotlin.load.java.lazy.resolveAnnotations
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaPackage
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryPackageSourceElement
@@ -48,6 +49,11 @@ class LazyJavaPackageFragment(
onRecursiveCall = listOf()
)
private val packageAnnotations = c.resolveAnnotations(jPackage)
// Test only method
fun getPackageAnnotations() = packageAnnotations
internal fun getSubPackageFqNames(): List<FqName> = subPackages()
internal fun findClassifierByJavaClass(jClass: JavaClass): ClassDescriptor? = scope.javaScope.findClassifierByJavaClass(jClass)
@@ -63,6 +63,7 @@ fun JavaAnnotationOwner.buildLazyValueForMap() = lazy {
annotations.associateBy { it.classId?.asSingleFqName() }
}
interface JavaPackage : JavaElement, JavaAnnotationOwner {
val fqName: FqName
val subPackages: Collection<JavaPackage>
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.load.java.structure.reflect
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaPackage
import org.jetbrains.kotlin.name.FqName
@@ -33,6 +34,14 @@ class ReflectJavaPackage(override val fqName: FqName) : ReflectJavaElement(), Ja
return listOf()
}
// TODO: support it if possible
override val annotations get() = emptyList<JavaAnnotation>()
override fun findAnnotation(fqName: FqName) = null
override val isDeprecatedInJavaDoc: Boolean
get() = false
override fun equals(other: Any?) = other is ReflectJavaPackage && fqName == other.fqName
override fun hashCode() = fqName.hashCode()