172: [Spring?] Revert "SpringKotlinAutowiringInspection: getting rid of SpringJavaInjectionPointsAutowiringInspection"

This reverts commit d0446b0aed0707ef616484c9fda04915c08598b0.
This commit is contained in:
Nikolay Krasko
2018-01-11 20:12:13 +03:00
parent 1a8b58cd14
commit f6e829d7f7
@@ -0,0 +1,182 @@
package org.jetbrains.kotlin.idea.spring.inspections
import com.intellij.codeInsight.AnnotationUtil
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.template.*
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.OpenFileDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.util.PropertyUtil
import com.intellij.spring.CommonSpringModel
import com.intellij.spring.SpringBundle
import com.intellij.spring.model.SpringBeanPointer
import com.intellij.spring.model.converters.SpringConverterUtil
import com.intellij.spring.model.highlighting.autowire.SpringJavaInjectionPointsAutowiringInspection
import com.intellij.spring.model.utils.SpringAutowireUtil
import org.jetbrains.kotlin.asJava.elements.KtLightField
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.asJava.toLightElements
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.inspections.registerWithElementsUnwrapped
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
class SpringKotlinAutowiringInspection : AbstractKotlinInspection() {
// Based on SpringJavaInjectionPointsAutowiringInspection.AddSpringBeanQualifierFix
class AddQualifierFix(
modifierListOwner: KtModifierListOwner,
private val beanPointers: Collection<SpringBeanPointer<*>>,
private val annotationFqName: String
) : LocalQuickFix {
private val elementPointer = modifierListOwner.createSmartPointer()
override fun getName() = SpringBundle.message("SpringAutowiringInspection.add.qualifier.fix")
override fun getFamilyName() = name
private fun getQualifierNamesSuggestNamesExpression(expression: KtStringTemplateExpression): Expression {
return object : Expression() {
override fun calculateResult(context: ExpressionContext): Result? {
PsiDocumentManager.getInstance(context.project).commitAllDocuments()
return TextResult(expression.plainContent)
}
override fun calculateQuickResult(context: ExpressionContext) = this.calculateResult(context)
override fun calculateLookupItems(context: ExpressionContext): Array<LookupElement>? {
PsiDocumentManager.getInstance(context.project).commitAllDocuments()
return beanPointers.sortedBy { it.name ?: "" }.mapNotNull { SpringConverterUtil.createCompletionVariant(it) }.toTypedArray()
}
}
}
private fun createQualifierNameTemplate(expression: KtStringTemplateExpression): Template {
val builder = TemplateBuilderImpl(expression.containingFile)
builder.replaceRange(
expression.getContentRange().shiftRight(expression.startOffset),
getQualifierNamesSuggestNamesExpression(expression)
)
return builder.buildInlineTemplate()
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val modifierListOwner = elementPointer.element ?: return
if (!FileModificationService.getInstance().preparePsiElementForWrite(modifierListOwner)) return
if (beanPointers.isEmpty()) return
val defaultBeanName = with(beanPointers.first().name) { if (isNullOrBlank()) "Unknown" else this }
val entry = KtPsiFactory(project).createAnnotationEntry("@$annotationFqName(\"$defaultBeanName\")")
val addedEntry = modifierListOwner.addAnnotationEntry(entry)
ShortenReferences.DEFAULT.process(addedEntry)
val stringTemplate = addedEntry.valueArguments.first().getArgumentExpression() as? KtStringTemplateExpression ?: return
val virtualFile = modifierListOwner.containingFile.virtualFile!!
val editor = FileEditorManager.getInstance(project).openTextEditor(OpenFileDescriptor(project, virtualFile, 0), false)!!
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document)
TemplateManager.getInstance(project).startTemplate(editor, createQualifierNameTemplate(stringTemplate))
}
}
private val javaInspection by lazy { SpringJavaInjectionPointsAutowiringInspection() }
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() {
// TODO: SpringJavaInjectionPointsAutowiringInspection.checkAutowiredMethod() is not accessible here
private fun checkAutowiredMethod(psiMethod: PsiMethod, holder: ProblemsHolder, springModel: CommonSpringModel, required: Boolean) {
val resourceAnnotation = SpringAutowireUtil.getResourceAnnotation(psiMethod)
when {
resourceAnnotation != null -> {
val propertyType = PropertyUtil.getPropertyType(psiMethod) ?: return
SpringJavaInjectionPointsAutowiringInspection.checkInjectionPoint(psiMethod, propertyType, holder, springModel, required)
}
psiMethod.parameterList.parametersCount == 0 &&
SpringAutowireUtil.isAutowiredByAnnotation(psiMethod) -> {
val nameIdentifier = psiMethod.nameIdentifier ?: return
val message = SpringBundle.message("bean.autowiring.by.type.no.parameter.for.autowired.method",
if (psiMethod.isConstructor) "constructor" else "method")
holder.registerProblem(nameIdentifier, message)
}
else -> {
for (parameter in psiMethod.parameterList.parameters) {
if (AnnotationUtil.isAnnotated(parameter, "org.springframework.beans.factory.annotation.Value", true)) continue
SpringJavaInjectionPointsAutowiringInspection.checkInjectionPoint(parameter, parameter.type, holder, springModel, required)
}
}
}
}
private fun Array<ProblemDescriptor>.registerAdjustedProblems() {
registerWithElementsUnwrapped(holder, isOnTheFly) { qf, element ->
// Can't access AddSpringBeanQualifierFix class directly
val klass = qf.javaClass
if (!klass.name.endsWith("AddSpringBeanQualifierFix")) return@registerWithElementsUnwrapped qf
try {
val fields = klass.declaredFields
@Suppress("UNCHECKED_CAST")
val beanPointers = fields[1].apply { isAccessible = true }.get(qf) as Collection<SpringBeanPointer<*>>
val annotationFqName = fields[2].apply { isAccessible = true }.get(qf) as String
val modifierListOwner = element.getNonStrictParentOfType<KtModifierListOwner>()
?: return@registerWithElementsUnwrapped qf
AddQualifierFix(modifierListOwner, beanPointers, annotationFqName)
}
catch (e: Exception) {
return@registerWithElementsUnwrapped null
}
}
}
private fun <T : PsiMember> T.processLightMember(
action: T.(holder: ProblemsHolder, model: CommonSpringModel, required: Boolean) -> Unit
) {
val model = SpringAutowireUtil.getProcessingSpringModel(containingClass) ?: return
val required = SpringAutowireUtil.isRequired(this)
val tmpHolder = ProblemsHolder(holder.manager, containingFile, isOnTheFly)
action(this, tmpHolder, model, required)
tmpHolder.resultsArray.registerAdjustedProblems()
}
private fun PsiMethod.processLightMethod() {
if (!SpringAutowireUtil.isInjectionPoint(this)) return
processLightMember { holder, model, required -> checkAutowiredMethod(this, holder, model, required) }
}
private fun PsiField.processLightField() {
if (!SpringAutowireUtil.isAutowiredByAnnotation(this)) return
processLightMember { holder, model, required ->
SpringJavaInjectionPointsAutowiringInspection.checkInjectionPoint(this, type, holder, model, required)
}
}
private fun PsiClass.processLightClass() {
javaInspection.checkClass(this, holder.manager, isOnTheFly)?.registerAdjustedProblems()
}
override fun visitNamedFunction(function: KtNamedFunction) {
function.toLightMethods().firstOrNull()?.processLightMethod()
}
override fun visitProperty(property: KtProperty) {
if (property.name != null) // It is here because `lightElement.name` returns `<no name provided>` instead of `null` suddenly
for (lightElement in property.toLightElements()) {
when (lightElement) {
is KtLightMethod -> lightElement.processLightMethod()
is KtLightField -> lightElement.processLightField()
}
}
}
override fun visitClassOrObject(classOrObject: KtClassOrObject) {
classOrObject.toLightClass()?.processLightClass()
}
}
}