Support package level annotations in Java
#KT-10942 In Progress
This commit is contained in:
+1
-1
@@ -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> =
|
||||
|
||||
+9
-3
@@ -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) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -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()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+12
-5
@@ -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
|
||||
|
||||
+1
-1
@@ -154,4 +154,4 @@ class TreeBasedClass(
|
||||
|
||||
override fun findInnerClass(name: Name) = innerClasses[name]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -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")
|
||||
}
|
||||
+6
@@ -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)
|
||||
|
||||
+1
@@ -63,6 +63,7 @@ fun JavaAnnotationOwner.buildLazyValueForMap() = lazy {
|
||||
annotations.associateBy { it.classId?.asSingleFqName() }
|
||||
}
|
||||
|
||||
interface JavaPackage : JavaElement, JavaAnnotationOwner {
|
||||
val fqName: FqName
|
||||
val subPackages: Collection<JavaPackage>
|
||||
|
||||
|
||||
+9
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user