J2K KotlinJavaFileStubProvider: refactor code

This commit is contained in:
Pavel V. Talanov
2015-10-30 20:36:14 +03:00
parent 75b8985468
commit 7e091d0df4
@@ -54,11 +54,13 @@ import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.utils.sure
class KotlinJavaFileStubProvider<T : WithFileStubAndExtraDiagnostics> private constructor(
private val project: Project,
private val local: Boolean,
private val stubGenerationStrategy: StubGenerationStrategy<T>) : CachedValueProvider<T> {
private val stubGenerationStrategy: StubGenerationStrategy<T>
) : CachedValueProvider<T> {
override fun compute(): CachedValueProvider.Result<T>? {
val packageFqName = stubGenerationStrategy.packageFqName
@@ -73,7 +75,7 @@ class KotlinJavaFileStubProvider<T : WithFileStubAndExtraDiagnostics> private co
val forExtraDiagnostics = BindingTraceContext()
try {
val stubStack = Stack<StubElement<PsiElement>>()
stubStack.push(javaFileStub)
stubStack.push(javaFileStub as StubElement<PsiElement>)
val state = GenerationState(
project,
@@ -109,255 +111,73 @@ class KotlinJavaFileStubProvider<T : WithFileStubAndExtraDiagnostics> private co
val extraDiagnostics = forExtraDiagnostics.bindingContext.diagnostics
return CachedValueProvider.Result.create(
stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
if (local) PsiModificationTracker.MODIFICATION_COUNT else PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT)
if (local) PsiModificationTracker.MODIFICATION_COUNT else PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
)
}
private fun createJavaFileStub(packageFqName: FqName, files: Collection<KtFile>): PsiJavaFileStub {
val javaFileStub = PsiJavaFileStubImpl(packageFqName.asString(), true)
javaFileStub.psiFactory = ClsWrapperStubPsiFactory()
val fakeFile = createFakeClsFile(project, packageFqName, files, object : Function0<PsiClassHolderFileStub<PsiFile>> {
override fun invoke(): PsiClassHolderFileStub<PsiFile> {
val manager = PsiManager.getInstance(project)
val virtualFile = getRepresentativeVirtualFile(files)
val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, virtualFile)) {
override fun getStub(): PsiClassHolderFileStub<*> {
return javaFileStub
}
})
override fun getPackageName(): String {
return packageFqName.asString()
}
}
fakeFile.isPhysical = false
javaFileStub.setPsi(fakeFile)
return javaFileStub
}
private fun getRepresentativeVirtualFile(files: Collection<KtFile>): VirtualFile {
return files.firstOrNull()?.virtualFile.sure { "No virtual file for " + files.iterator().next() }
}
private fun checkForBuiltIns(fqName: FqName, files: Collection<KtFile>) {
for (file in files) {
if (LightClassUtil.belongsToKotlinBuiltIns(file)) {
// We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
// If it fails later, there will be an exception logged
logErrorWithOSInfo(null, fqName, file.virtualFile)
}
}
}
private fun logErrorWithOSInfo(cause: Throwable?, fqName: FqName, virtualFile: VirtualFile?) {
val path = if (virtualFile == null) "<null>" else virtualFile.path
LOG.error(
"Could not generate LightClass for $fqName declared in $path\nbuilt-ins dir URL is ${LightClassUtil.builtInsDirUrl}\n" +
"System: ${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION} Java Runtime: ${SystemInfo.JAVA_RUNTIME_VERSION}",
cause
)
}
companion object {
fun createForDeclaredClass(classOrObject: KtClassOrObject): KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> {
return KotlinJavaFileStubProvider(
classOrObject.project,
classOrObject.isLocal(),
StubGenerationStrategyForDeclaredClass(classOrObject)
)
}
fun createForFacadeClass(
project: Project,
facadeFqName: FqName,
searchScope: GlobalSearchScope): CachedValueProvider<KotlinFacadeLightClassData> {
return KotlinJavaFileStubProvider(
project,
false,
object : StubGenerationStrategy<KotlinFacadeLightClassData> {
override val files: Collection<KtFile>
get() = LightClassGenerationSupport.getInstance(project).findFilesForFacade(facadeFqName, searchScope)
override val packageFqName: FqName
get() = facadeFqName.parent()
override fun getContext(files: Collection<KtFile>): LightClassConstructionContext {
return LightClassGenerationSupport.getInstance(project).getContextForFacade(files)
}
override fun createLightClassData(
javaFileStub: PsiJavaFileStub,
bindingContext: BindingContext,
extraDiagnostics: Diagnostics): KotlinFacadeLightClassData {
return KotlinFacadeLightClassData(javaFileStub, extraDiagnostics)
}
override val generateClassFilter: GenerationState.GenerateClassFilter
get() = object : GenerationState.GenerateClassFilter() {
override fun shouldAnnotateClass(classOrObject: KtClassOrObject): Boolean {
return shouldGenerateClass(classOrObject)
}
override fun shouldGenerateClass(classOrObject: KtClassOrObject): Boolean {
return KtPsiUtil.isLocal(classOrObject)
}
override fun shouldGeneratePackagePart(jetFile: KtFile): Boolean {
return true
}
override fun shouldGenerateScript(script: KtScript): Boolean {
return false
}
}
override fun generate(state: GenerationState, files: Collection<KtFile>) {
if (!files.isEmpty()) {
val representativeFile = files.iterator().next()
val fileClassInfo = NoResolveFileClassesProvider.getFileClassInfo(representativeFile)
if (!fileClassInfo.withJvmMultifileClass) {
val codegen = state.factory.forPackage(representativeFile.packageFqName, files)
codegen.generate(CompilationErrorHandler.THROW_EXCEPTION)
state.factory.asList()
return
}
}
val codegen = state.factory.forMultifileClass(facadeFqName, files)
codegen.generate(CompilationErrorHandler.THROW_EXCEPTION)
state.factory.asList()
}
override fun toString(): String {
return StubGenerationStrategy::class.java.name + " for facade class"
}
})
}
fun createForDeclaredClass(classOrObject: KtClassOrObject): KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> {
return KotlinJavaFileStubProvider(
classOrObject.project,
classOrObject.isLocal(),
object : StubGenerationStrategy<OutermostKotlinClassLightClassData> {
private val file: KtFile
get() = classOrObject.getContainingKtFile()
override fun getContext(files: Collection<KtFile>): LightClassConstructionContext {
return LightClassGenerationSupport.getInstance(classOrObject.project).getContextForClassOrObject(classOrObject)
}
override fun createLightClassData(
javaFileStub: PsiJavaFileStub,
bindingContext: BindingContext,
extraDiagnostics: Diagnostics): OutermostKotlinClassLightClassData {
val classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject) ?: return OutermostKotlinClassLightClassData(
javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject,
emptyMap<KtClassOrObject, InnerKotlinClassLightClassData>())
val fqName = predictClassFqName(bindingContext, classDescriptor)
val allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor)
val innerClassesMap = ContainerUtil.newHashMap<KtClassOrObject, InnerKotlinClassLightClassData>()
for (innerClassDescriptor in allInnerClasses) {
val declaration = descriptorToDeclaration(innerClassDescriptor)
if (declaration !is KtClassOrObject) continue
val innerLightClassData = InnerKotlinClassLightClassData(
predictClassFqName(bindingContext, innerClassDescriptor),
declaration)
innerClassesMap.put(declaration, innerLightClassData)
}
return OutermostKotlinClassLightClassData(
javaFileStub,
extraDiagnostics,
fqName,
classOrObject,
innerClassesMap)
}
private fun predictClassFqName(bindingContext: BindingContext, classDescriptor: ClassDescriptor): FqName {
val asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor)
//noinspection ConstantConditions
return JvmClassName.byInternalName(asmType.className.replace('.', '/')).fqNameForClassNameWithoutDollars
}
override val files: Collection<KtFile>
get() = listOf(file)
override val packageFqName: FqName
get() = file.packageFqName
override // Trivial: generate and analyze class we are interested in.
// Process all parent classes as they are context for current class
// Process child classes because they probably affect members (heuristic)
// Local classes should be process by CodegenAnnotatingVisitor to
// decide what class they should be placed in.
//
// Example:
// class A
// fun foo() {
// trait Z: A {}
// fun bar() {
// class <caret>O2: Z {}
// }
// }
// TODO: current method will process local classes in irrelevant declarations, it should be fixed.
// We generate all enclosing classes
val generateClassFilter: GenerationState.GenerateClassFilter
get() = object : GenerationState.GenerateClassFilter() {
override fun shouldGeneratePackagePart(jetFile: KtFile): Boolean {
return true
}
override fun shouldAnnotateClass(classOrObject: KtClassOrObject): Boolean {
return shouldGenerateClass(classOrObject)
}
override fun shouldGenerateClass(generatedClassOrObject: KtClassOrObject): Boolean {
if (generatedClassOrObject === classOrObject) return true
if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) || PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
return true
}
if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
val commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject)
return commonParent != null && commonParent !is PsiFile
}
return false
}
override fun shouldGenerateScript(script: KtScript): Boolean {
return PsiTreeUtil.isAncestor(script, classOrObject, false)
}
}
override fun generate(state: GenerationState, files: Collection<KtFile>) {
val packageCodegen = state.factory.forPackage(packageFqName, files)
val file = classOrObject.getContainingKtFile()
val packagePartType = state.fileClassesProvider.getFileClassType(file)
val context = state.rootContext.intoPackagePart(packageCodegen.packageFragment, packagePartType, file)
packageCodegen.generateClassOrObject(classOrObject, context)
state.factory.asList()
}
override fun toString(): String {
return StubGenerationStrategy::class.java.name + " for explicit class " + classOrObject.name
}
})
return KotlinJavaFileStubProvider(project, false, StubGenerationStrategyForFacadeClass(project, facadeFqName, searchScope))
}
private val LOG = Logger.getInstance(KotlinJavaFileStubProvider::class.java)
private fun createFakeClsFile(
project: Project,
packageFqName: FqName,
files: Collection<KtFile>,
fileStubProvider: Function0<PsiClassHolderFileStub<PsiFile>>): ClsFileImpl {
val manager = PsiManager.getInstance(project)
val virtualFile = getRepresentativeVirtualFile(files)
val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, virtualFile)) {
override fun getStub(): PsiClassHolderFileStub<PsiFile> {
return fileStubProvider.invoke()
}
override fun getPackageName(): String {
return packageFqName.asString()
}
}
fakeFile.isPhysical = false
return fakeFile
}
private fun getRepresentativeVirtualFile(files: Collection<KtFile>): VirtualFile {
val firstFile = files.iterator().next()
val virtualFile = firstFile.virtualFile
assert(virtualFile != null) { "No virtual file for " + firstFile }
return virtualFile
}
private fun checkForBuiltIns(fqName: FqName, files: Collection<KtFile>) {
for (file in files) {
if (LightClassUtil.belongsToKotlinBuiltIns(file)) {
// We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
// If it fails later, there will be an exception logged
logErrorWithOSInfo(null, fqName, file.virtualFile)
}
}
}
private fun logErrorWithOSInfo(cause: Throwable?, fqName: FqName, virtualFile: VirtualFile?) {
val path = if (virtualFile == null) "<null>" else virtualFile.path
LOG.error(
"Could not generate LightClass for " + fqName + " declared in " + path + "\n" + "built-ins dir URL is " + LightClassUtil.builtInsDirUrl + "\n" + "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
cause)
}
}
}
@@ -371,3 +191,178 @@ private interface StubGenerationStrategy<T : WithFileStubAndExtraDiagnostics> {
val generateClassFilter: GenerationState.GenerateClassFilter
fun generate(state: GenerationState, files: Collection<KtFile>)
}
private class StubGenerationStrategyForDeclaredClass(private val classOrObject: KtClassOrObject) : StubGenerationStrategy<OutermostKotlinClassLightClassData> {
private val file: KtFile
get() = classOrObject.getContainingKtFile()
override fun getContext(files: Collection<KtFile>): LightClassConstructionContext {
return LightClassGenerationSupport.getInstance(classOrObject.project).getContextForClassOrObject(classOrObject)
}
override fun createLightClassData(
javaFileStub: PsiJavaFileStub,
bindingContext: BindingContext,
extraDiagnostics: Diagnostics): OutermostKotlinClassLightClassData {
val classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject) ?: return OutermostKotlinClassLightClassData(
javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject,
emptyMap<KtClassOrObject, InnerKotlinClassLightClassData>())
val fqName = predictClassFqName(bindingContext, classDescriptor)
val allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor)
val innerClassesMap = ContainerUtil.newHashMap<KtClassOrObject, InnerKotlinClassLightClassData>()
for (innerClassDescriptor in allInnerClasses) {
val declaration = descriptorToDeclaration(innerClassDescriptor)
if (declaration !is KtClassOrObject) continue
val innerLightClassData = InnerKotlinClassLightClassData(
predictClassFqName(bindingContext, innerClassDescriptor),
declaration)
innerClassesMap.put(declaration, innerLightClassData)
}
return OutermostKotlinClassLightClassData(
javaFileStub,
extraDiagnostics,
fqName,
classOrObject,
innerClassesMap)
}
private fun predictClassFqName(bindingContext: BindingContext, classDescriptor: ClassDescriptor): FqName {
val asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor)
//noinspection ConstantConditions
return JvmClassName.byInternalName(asmType.className.replace('.', '/')).fqNameForClassNameWithoutDollars
}
override val files: Collection<KtFile>
get() = listOf(file)
override val packageFqName: FqName
get() = file.packageFqName
override val generateClassFilter: GenerationState.GenerateClassFilter
get() = object : GenerationState.GenerateClassFilter() {
override fun shouldGeneratePackagePart(jetFile: KtFile): Boolean {
return true
}
override fun shouldAnnotateClass(classOrObject: KtClassOrObject): Boolean {
return shouldGenerateClass(classOrObject)
}
override fun shouldGenerateClass(classOrObject: KtClassOrObject): Boolean {
// Trivial: generate and analyze class we are interested in.
if (classOrObject === classOrObject) return true
// Process all parent classes as they are context for current class
// Process child classes because they probably affect members (heuristic)
if (PsiTreeUtil.isAncestor(classOrObject, classOrObject, true) || PsiTreeUtil.isAncestor(classOrObject, classOrObject, true)) {
return true
}
// Local classes should be process by CodegenAnnotatingVisitor to
// decide what class they should be placed in.
//
// Example:
// class A
// fun foo() {
// trait Z: A {}
// fun bar() {
// class <caret>O2: Z {}
// }
// }
// TODO: current method will process local classes in irrelevant declarations, it should be fixed.
// We generate all enclosing classes
if (classOrObject.isLocal() && classOrObject.isLocal()) {
val commonParent = PsiTreeUtil.findCommonParent(classOrObject, classOrObject)
return commonParent != null && commonParent !is PsiFile
}
return false
}
override fun shouldGenerateScript(script: KtScript): Boolean {
return PsiTreeUtil.isAncestor(script, classOrObject, false)
}
}
override fun generate(state: GenerationState, files: Collection<KtFile>) {
val packageCodegen = state.factory.forPackage(packageFqName, files)
val file = classOrObject.getContainingKtFile()
val packagePartType = state.fileClassesProvider.getFileClassType(file)
val context = state.rootContext.intoPackagePart(packageCodegen.packageFragment, packagePartType, file)
packageCodegen.generateClassOrObject(classOrObject, context)
state.factory.asList()
}
override fun toString(): String {
return StubGenerationStrategy::class.java.name + " for explicit class " + classOrObject.name
}
}
private class StubGenerationStrategyForFacadeClass(
private val project: Project, private val facadeFqName: FqName, private val searchScope: GlobalSearchScope
) : StubGenerationStrategy<KotlinFacadeLightClassData> {
override val files: Collection<KtFile>
get() = LightClassGenerationSupport.getInstance(project).findFilesForFacade(facadeFqName, searchScope)
override val packageFqName: FqName
get() = facadeFqName.parent()
override fun getContext(files: Collection<KtFile>): LightClassConstructionContext {
return LightClassGenerationSupport.getInstance(project).getContextForFacade(files)
}
override fun createLightClassData(
javaFileStub: PsiJavaFileStub,
bindingContext: BindingContext,
extraDiagnostics: Diagnostics): KotlinFacadeLightClassData {
return KotlinFacadeLightClassData(javaFileStub, extraDiagnostics)
}
override val generateClassFilter: GenerationState.GenerateClassFilter
get() = object : GenerationState.GenerateClassFilter() {
override fun shouldAnnotateClass(classOrObject: KtClassOrObject): Boolean {
return shouldGenerateClass(classOrObject)
}
override fun shouldGenerateClass(classOrObject: KtClassOrObject): Boolean {
return KtPsiUtil.isLocal(classOrObject)
}
override fun shouldGeneratePackagePart(jetFile: KtFile): Boolean {
return true
}
override fun shouldGenerateScript(script: KtScript): Boolean {
return false
}
}
override fun generate(state: GenerationState, files: Collection<KtFile>) {
if (!files.isEmpty()) {
val representativeFile = files.iterator().next()
val fileClassInfo = NoResolveFileClassesProvider.getFileClassInfo(representativeFile)
if (!fileClassInfo.withJvmMultifileClass) {
val codegen = state.factory.forPackage(representativeFile.packageFqName, files)
codegen.generate(CompilationErrorHandler.THROW_EXCEPTION)
state.factory.asList()
return
}
}
val codegen = state.factory.forMultifileClass(facadeFqName, files)
codegen.generate(CompilationErrorHandler.THROW_EXCEPTION)
state.factory.asList()
}
override fun toString(): String {
return StubGenerationStrategy::class.java.name + " for facade class"
}
}