172: [Spring] Revert "Fixes for 173 after 28.08.17 changes in IDEA (#1271)"
This reverts commit 3a37d18e5d65043fc99a28772303d948e0f89f6c.
This commit is contained in:
+512
@@ -0,0 +1,512 @@
|
|||||||
|
/*
|
||||||
|
* 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.idea.spring.generate
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.lookup.LookupElement
|
||||||
|
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||||
|
import com.intellij.codeInsight.template.Expression
|
||||||
|
import com.intellij.codeInsight.template.ExpressionContext
|
||||||
|
import com.intellij.codeInsight.template.TemplateBuilderImpl
|
||||||
|
import com.intellij.codeInsight.template.TextResult
|
||||||
|
import com.intellij.codeInspection.SmartHashMap
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.application.runWriteAction
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.psi.*
|
||||||
|
import com.intellij.psi.util.PropertyUtil
|
||||||
|
import com.intellij.psi.util.PsiTypesUtil
|
||||||
|
import com.intellij.spring.CommonSpringModel
|
||||||
|
import com.intellij.spring.model.CommonSpringBean
|
||||||
|
import com.intellij.spring.model.SpringBeanPointer
|
||||||
|
import com.intellij.spring.model.SpringModelSearchParameters
|
||||||
|
import com.intellij.spring.model.actions.generate.GenerateSpringBeanDependenciesUtil
|
||||||
|
import com.intellij.spring.model.actions.generate.GenerateSpringBeanDependenciesUtil.*
|
||||||
|
import com.intellij.spring.model.actions.generate.SpringBeanClassMember
|
||||||
|
import com.intellij.spring.model.highlighting.SpringConstructorArgResolveUtil
|
||||||
|
import com.intellij.spring.model.utils.SpringBeanCoreUtils
|
||||||
|
import com.intellij.spring.model.utils.SpringBeanUtils
|
||||||
|
import com.intellij.spring.model.utils.SpringModelSearchers
|
||||||
|
import com.intellij.spring.model.utils.SpringModelUtils
|
||||||
|
import com.intellij.spring.model.xml.DomSpringBean
|
||||||
|
import com.intellij.spring.model.xml.beans.Beans
|
||||||
|
import com.intellij.spring.model.xml.beans.SpringBean
|
||||||
|
import com.intellij.util.IncorrectOperationException
|
||||||
|
import com.intellij.util.xml.DomElement
|
||||||
|
import com.intellij.util.xml.DomUtil
|
||||||
|
import org.jetbrains.annotations.NotNull
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
|
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||||
|
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
|
||||||
|
import org.jetbrains.kotlin.idea.caches.project.moduleInfo
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.*
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaOrKotlinMemberDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.util.getParameterDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.completion.BasicLookupElementFactory
|
||||||
|
import org.jetbrains.kotlin.idea.completion.InsertHandlerProvider
|
||||||
|
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||||
|
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
|
||||||
|
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||||
|
import org.jetbrains.kotlin.idea.core.getOrCreateCompanionObject
|
||||||
|
import org.jetbrains.kotlin.idea.editor.BatchTemplateRunner
|
||||||
|
import org.jetbrains.kotlin.idea.spring.beanClass
|
||||||
|
import org.jetbrains.kotlin.idea.spring.effectiveBeanClasses
|
||||||
|
import org.jetbrains.kotlin.idea.util.CallType
|
||||||
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
|
import org.jetbrains.kotlin.load.java.propertyNameBySetMethodName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.expressions.TypeReconstructionUtil
|
||||||
|
import org.jetbrains.kotlin.utils.SmartList
|
||||||
|
import org.jetbrains.kotlin.utils.ifEmpty
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
// TODO: GenerateSpringBeanDependenciesUtil.ensureFileWritable() is not accessible here
|
||||||
|
private fun DomElement.ensureFileWritable() = ensureFileWritable(DomUtil.getFile(this).virtualFile, manager.project)
|
||||||
|
|
||||||
|
// TODO: GenerateSpringBeanDependenciesUtil.getReferenceName() is not accessible here
|
||||||
|
private fun getReferencedName(currentBean: SpringBean, bean: SpringBeanPointer<CommonSpringBean>): String? {
|
||||||
|
val model = SpringModelUtils.getInstance().getSpringModel(currentBean)
|
||||||
|
return SpringBeanCoreUtils.getReferencedName(bean, model.allCommonBeans)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: GenerateSpringBeanDependenciesUtil.getExistedSetter() is not accessible here
|
||||||
|
private fun getExistedSetter(currentBeanClass: PsiClass, setterPsiClass: PsiClass): PsiMethod? {
|
||||||
|
val psiClassType = JavaPsiFacade.getInstance(setterPsiClass.project).elementFactory.createType(setterPsiClass)
|
||||||
|
return currentBeanClass.allMethods.firstOrNull {
|
||||||
|
PropertyUtil.isSimplePropertySetter(it) && it.parameterList.parameters.first().type.isAssignableFrom(psiClassType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: GenerateSpringBeanDependenciesUtil.findConstructor() is not accessible here
|
||||||
|
private fun findConstructor(constructors: Array<PsiMethod>, psiParameterTypes: List<PsiType>): PsiMethod? {
|
||||||
|
return constructors.firstOrNull {
|
||||||
|
val parameters = it.parameterList.parameters
|
||||||
|
if (it.parameterList.parametersCount != psiParameterTypes.size) return@firstOrNull false
|
||||||
|
(psiParameterTypes zip parameters).all { it.first.isAssignableFrom(it.second.type) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: GenerateSpringBeanDependenciesUtil.findExistedConstructor() is not accessible here
|
||||||
|
private fun findExistedConstructor(
|
||||||
|
currentBean: SpringBean,
|
||||||
|
currentBeanClass: PsiClass,
|
||||||
|
candidateParameterClasses: Array<out PsiClass>
|
||||||
|
): PsiMethod? {
|
||||||
|
val constructors = SpringConstructorArgResolveUtil.findMatchingMethods(currentBean)
|
||||||
|
for (candidateBeanClass in candidateParameterClasses) {
|
||||||
|
for (constructor in constructors) {
|
||||||
|
val psiParameterTypes = SmartList<PsiType>().apply {
|
||||||
|
constructor.parameterList.parameters.mapTo(this) { it.type }
|
||||||
|
add(PsiTypesUtil.getClassType(candidateBeanClass))
|
||||||
|
}
|
||||||
|
findConstructor(currentBeanClass.constructors, psiParameterTypes)?.let { return it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: GenerateSpringBeanDependenciesUtil.createSpringBean() is not accessible here
|
||||||
|
private fun createSpringBean(parentBeans: Beans, psiClass: PsiClass): SpringBean? {
|
||||||
|
if (!((parentBeans as DomElement)).ensureFileWritable()) return null
|
||||||
|
|
||||||
|
return parentBeans.addBean().apply {
|
||||||
|
clazz.stringValue = psiClass.qualifiedName
|
||||||
|
id.stringValue = SpringBeanCoreUtils.suggestBeanNames(this).firstOrNull() ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SpringBean.getFactoryFunctionName(factoryBeanClass: KtClass): String {
|
||||||
|
factoryMethod.stringValue?.let { if (!it.isBlank()) return it }
|
||||||
|
|
||||||
|
val existingNames = factoryBeanClass.declarations.mapNotNull { if (it is KtFunction || it is KtClassOrObject) it.name else null }
|
||||||
|
return KotlinNameSuggester.suggestNameByName("create${beanClass()!!.name}") { it !in existingNames }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val PsiClass.defaultTypeText: String
|
||||||
|
get() {
|
||||||
|
val qName = qualifiedName ?: return "Any"
|
||||||
|
val typeParameters = typeParameters
|
||||||
|
if (typeParameters.isEmpty()) return qName
|
||||||
|
return TypeReconstructionUtil.getTypeNameAndStarProjectionsString(qName, typeParameters.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSettable(
|
||||||
|
candidateBean: SpringBeanPointer<CommonSpringBean>,
|
||||||
|
currentBeanClass: KtLightClass,
|
||||||
|
candidateBeanClasses: Array<out PsiClass>,
|
||||||
|
injectionKind: SpringDependencyInjectionKind
|
||||||
|
): KtNamedDeclaration {
|
||||||
|
val project = currentBeanClass.project
|
||||||
|
val psiFactory = KtPsiFactory(project)
|
||||||
|
val beanName = candidateBean.name
|
||||||
|
try {
|
||||||
|
val candidateClass = candidateBeanClasses.first()
|
||||||
|
val propertyName = if (beanName != null && beanName.isIdentifier()) beanName else candidateClass.name!!
|
||||||
|
|
||||||
|
val prototype: KtNamedDeclaration = when (injectionKind) {
|
||||||
|
SpringDependencyInjectionKind.SETTER -> {
|
||||||
|
psiFactory.createFunction("fun set${propertyName.capitalize()}(${propertyName.decapitalize()}: ${candidateClass.defaultTypeText}) { }")
|
||||||
|
}
|
||||||
|
|
||||||
|
SpringDependencyInjectionKind.LATEINIT_PROPERTY -> {
|
||||||
|
psiFactory.createProperty("lateinit var ${propertyName.decapitalize()}: ${candidateClass.defaultTypeText}")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("Unexpected injection kind: $injectionKind")
|
||||||
|
}
|
||||||
|
return currentBeanClass.kotlinOrigin!!.addDeclaration(prototype).apply { ShortenReferences.DEFAULT.process(this) }
|
||||||
|
}
|
||||||
|
catch (e: IncorrectOperationException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getSuggestedNames(
|
||||||
|
beanPointer: SpringBeanPointer<CommonSpringBean>,
|
||||||
|
declaration: KtCallableDeclaration,
|
||||||
|
excludedInValidator: List<KtDeclaration> = listOf(declaration),
|
||||||
|
existingNames: Collection<String> = emptyList(),
|
||||||
|
getType: CallableDescriptor.() -> KotlinType?
|
||||||
|
): Collection<String> {
|
||||||
|
val names = LinkedHashSet<String>()
|
||||||
|
|
||||||
|
val newDeclarationNameValidator =
|
||||||
|
NewDeclarationNameValidator(declaration.parent, null, NewDeclarationNameValidator.Target.VARIABLES, excludedInValidator)
|
||||||
|
fun validate(name: String) = name !in existingNames && newDeclarationNameValidator(name)
|
||||||
|
|
||||||
|
SpringBeanUtils.getInstance()
|
||||||
|
.findBeanNames(beanPointer.springBean)
|
||||||
|
.asSequence()
|
||||||
|
.filter { it.isIdentifier() }
|
||||||
|
.mapTo(names) { KotlinNameSuggester.suggestNameByName(it, ::validate) }
|
||||||
|
|
||||||
|
(declaration.resolveToDescriptor() as CallableDescriptor).getType()?.let {
|
||||||
|
names += KotlinNameSuggester.suggestNamesByType(it, ::validate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun TemplateBuilderImpl.appendVariableTemplate(
|
||||||
|
variable: KtCallableDeclaration,
|
||||||
|
candidateBeanClasses: Array<out PsiClass>,
|
||||||
|
computeSuggestions: (() -> Collection<String>)?
|
||||||
|
) {
|
||||||
|
if (computeSuggestions != null) {
|
||||||
|
val suggestedNames = computeSuggestions()
|
||||||
|
val defaultName = variable.name ?: ""
|
||||||
|
val nameExpression = object : Expression() {
|
||||||
|
override fun calculateResult(context: ExpressionContext) = TextResult(defaultName)
|
||||||
|
|
||||||
|
override fun calculateQuickResult(context: ExpressionContext) = calculateResult(context)
|
||||||
|
|
||||||
|
override fun calculateLookupItems(context: ExpressionContext): Array<LookupElement>? {
|
||||||
|
PsiDocumentManager.getInstance(context.project).commitAllDocuments()
|
||||||
|
return suggestedNames.map { LookupElementBuilder.create(it) }.toTypedArray()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replaceElement(variable.nameIdentifier, "names", nameExpression, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val superTypeVariants = getSuperTypeVariants(candidateBeanClasses)
|
||||||
|
if (superTypeVariants.size > 1) {
|
||||||
|
val lookupFactory = BasicLookupElementFactory(variable.project, InsertHandlerProvider(CallType.TYPE) { emptyList() })
|
||||||
|
val typeReferenceExpression = object : Expression() {
|
||||||
|
override fun calculateResult(context: ExpressionContext) = TextResult(candidateBeanClasses.first().qualifiedName ?: "")
|
||||||
|
|
||||||
|
override fun calculateQuickResult(context: ExpressionContext) = calculateResult(context)
|
||||||
|
|
||||||
|
override fun calculateLookupItems(context: ExpressionContext): Array<LookupElement>? {
|
||||||
|
return superTypeVariants
|
||||||
|
.mapTo(LinkedHashSet()) { lookupFactory.createLookupElementForJavaClass(it, qualifyNestedClasses = true) }
|
||||||
|
.toTypedArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replaceElement(variable.typeReference, "type", typeReferenceExpression, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BatchTemplateRunner.addCreateFunctionTemplate(
|
||||||
|
function: KtFunction,
|
||||||
|
candidateBeanClasses: Map<Int, Array<out PsiClass>>,
|
||||||
|
dependency: SpringBeanPointer<CommonSpringBean>
|
||||||
|
) {
|
||||||
|
val parameterList = function.valueParameterList!!
|
||||||
|
val builder = TemplateBuilderImpl(parameterList)
|
||||||
|
addTemplateFactory(parameterList) {
|
||||||
|
for ((paramIndex, candidateBeanClassesForParam) in candidateBeanClasses) {
|
||||||
|
builder.appendVariableTemplate(function.valueParameters[paramIndex], candidateBeanClassesForParam) {
|
||||||
|
getSuggestedNames(dependency, function, listOf(function.valueParameters[paramIndex], function)) {
|
||||||
|
valueParameters[paramIndex].type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.buildInlineTemplate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BatchTemplateRunner.addCreatePropertyTemplate(property: KtProperty, candidateBeanClasses: Array<out PsiClass>) {
|
||||||
|
val builder = TemplateBuilderImpl(property)
|
||||||
|
addTemplateFactory(property) {
|
||||||
|
builder.appendVariableTemplate(property, candidateBeanClasses, null)
|
||||||
|
builder.buildInlineTemplate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BatchTemplateRunner.addCreateFunctionTemplate(
|
||||||
|
function: KtFunction,
|
||||||
|
paramIndex: Int,
|
||||||
|
candidateBeanClasses: Array<out PsiClass>,
|
||||||
|
dependency: SpringBeanPointer<CommonSpringBean>
|
||||||
|
) {
|
||||||
|
addCreateFunctionTemplate(function, mapOf(paramIndex to candidateBeanClasses), dependency)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOrCreateSetter(
|
||||||
|
candidateBean: SpringBeanPointer<CommonSpringBean>,
|
||||||
|
currentBeanClass: KtLightClass,
|
||||||
|
candidateBeanClasses: Array<out PsiClass>,
|
||||||
|
templatesHolder: BatchTemplateRunner,
|
||||||
|
injectionKind: SpringDependencyInjectionKind
|
||||||
|
): PsiNamedElement? {
|
||||||
|
for (candidateBeanClass in candidateBeanClasses) {
|
||||||
|
getExistedSetter(currentBeanClass, candidateBeanClass)?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ensureFileWritable(currentBeanClass)) return null
|
||||||
|
val settable = createSettable(candidateBean, currentBeanClass, candidateBeanClasses, injectionKind)
|
||||||
|
when (settable) {
|
||||||
|
is KtNamedFunction -> templatesHolder.addCreateFunctionTemplate(settable, 0, candidateBeanClasses, candidateBean)
|
||||||
|
is KtProperty -> templatesHolder.addCreatePropertyTemplate(settable, candidateBeanClasses)
|
||||||
|
}
|
||||||
|
|
||||||
|
return settable
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSetterDependency(
|
||||||
|
currentBean: SpringBean,
|
||||||
|
dependency: SpringBeanPointer<CommonSpringBean>,
|
||||||
|
injectionKind: SpringDependencyInjectionKind
|
||||||
|
): BatchTemplateRunner? {
|
||||||
|
val templatesHolder = BatchTemplateRunner(currentBean.manager.project)
|
||||||
|
val currentBeanClass = currentBean.beanClass() as? KtLightClass ?: return null
|
||||||
|
val candidateBeanClasses = dependency.effectiveBeanClasses().ifEmpty { return null }
|
||||||
|
val setter = getOrCreateSetter(dependency, currentBeanClass, candidateBeanClasses, templatesHolder, injectionKind) ?: return null
|
||||||
|
currentBean.addProperty().apply {
|
||||||
|
name.ensureXmlElementExists()
|
||||||
|
name.stringValue = when (setter) {
|
||||||
|
is PsiMethod, is KtFunction -> propertyNameBySetMethodName(Name.identifier(setter.name!!), false)!!.asString()
|
||||||
|
else -> setter.name
|
||||||
|
}
|
||||||
|
refAttr.setStringValue(getReferencedName(currentBean, dependency))
|
||||||
|
}
|
||||||
|
return templatesHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addConstructorParameter(
|
||||||
|
currentBeanClass: KtLightClass,
|
||||||
|
candidateBeanClass: PsiClass,
|
||||||
|
constructor: KtFunction
|
||||||
|
) {
|
||||||
|
val psiFactory = KtPsiFactory(currentBeanClass)
|
||||||
|
|
||||||
|
val validator = NewDeclarationNameValidator(constructor, null, NewDeclarationNameValidator.Target.VARIABLES)
|
||||||
|
val resolutionFacade = currentBeanClass.kotlinOrigin!!.getResolutionFacade()
|
||||||
|
val defaultType = (candidateBeanClass.getJavaOrKotlinMemberDescriptor(resolutionFacade) as ClassDescriptor).defaultType
|
||||||
|
val name = KotlinNameSuggester.suggestNamesByType(defaultType, validator).first()
|
||||||
|
|
||||||
|
val parameter = psiFactory.createParameter("$name: ${candidateBeanClass.defaultTypeText}")
|
||||||
|
constructor.getValueParameterList()!!.addParameter(parameter).apply { ShortenReferences.DEFAULT.process(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findProperConstructorAndAddParameter(
|
||||||
|
currentBean: SpringBean,
|
||||||
|
dependency: SpringBeanPointer<CommonSpringBean>,
|
||||||
|
currentBeanClass: KtLightClass,
|
||||||
|
candidateBeanClass: PsiClass,
|
||||||
|
holder: BatchTemplateRunner
|
||||||
|
): PsiMethod? {
|
||||||
|
val lightConstructor = currentBean.resolvedConstructorArgs.resolvedMethod as? KtLightMethod ?: return null
|
||||||
|
val constructorOrigin = lightConstructor.kotlinOrigin
|
||||||
|
val properConstructor = when (constructorOrigin) {
|
||||||
|
is KtFunction -> constructorOrigin
|
||||||
|
is KtClass -> constructorOrigin.createPrimaryConstructorIfAbsent()
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
addConstructorParameter(currentBeanClass, candidateBeanClass, properConstructor)
|
||||||
|
holder.addCreateFunctionTemplate(properConstructor, properConstructor.getValueParameters().lastIndex, arrayOf(candidateBeanClass), dependency)
|
||||||
|
return lightConstructor
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createConstructorWithTemplate(
|
||||||
|
currentBean: SpringBean,
|
||||||
|
dependency: SpringBeanPointer<CommonSpringBean>,
|
||||||
|
templatesHolder: BatchTemplateRunner
|
||||||
|
) {
|
||||||
|
val beanClass = currentBean.beanClass() as? KtLightClass ?: return
|
||||||
|
val ktBeanClass = beanClass.kotlinOrigin as? KtClass ?: return
|
||||||
|
|
||||||
|
try {
|
||||||
|
val psiFactory = KtPsiFactory(beanClass)
|
||||||
|
val factoryFunction = when {
|
||||||
|
currentBean.factoryBean.exists() -> {
|
||||||
|
// TODO: Support non-Kotlin factory beans
|
||||||
|
val factoryClass = currentBean.factoryBean.value?.beanClass as? KtLightClass ?: return
|
||||||
|
val ktFactoryClass = factoryClass.kotlinOrigin as? KtClass ?: return
|
||||||
|
val funName = currentBean.getFactoryFunctionName(ktFactoryClass)
|
||||||
|
val funText = "fun $funName(): ${beanClass.defaultTypeText} {\n return null \n}"
|
||||||
|
ktFactoryClass.addDeclaration(psiFactory.createFunction(funText))
|
||||||
|
}
|
||||||
|
currentBean.factoryMethod.exists() -> {
|
||||||
|
val funName = currentBean.getFactoryFunctionName(ktBeanClass)
|
||||||
|
val funText = "@JvmStatic fun $funName(): ${beanClass.defaultTypeText} {\n return null \n}"
|
||||||
|
ktBeanClass.getOrCreateCompanionObject().addDeclaration(psiFactory.createFunction(funText))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
if (ktBeanClass.hasPrimaryConstructor() || ktBeanClass.getSecondaryConstructors().isNotEmpty()) {
|
||||||
|
ktBeanClass.addDeclaration(psiFactory.createSecondaryConstructor("constructor()"))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ktBeanClass.createPrimaryConstructorIfAbsent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as KtFunction
|
||||||
|
|
||||||
|
ShortenReferences.DEFAULT.process(factoryFunction)
|
||||||
|
|
||||||
|
val dummyMethod = JavaPsiFacade.getElementFactory(beanClass.project).createMethod("foo", PsiType.VOID)
|
||||||
|
dummyMethod.containingFile.moduleInfo = ktBeanClass.getModuleInfo()
|
||||||
|
SpringConstructorArgResolveUtil.suggestParamsForConstructorArgs(currentBean).forEach { dummyMethod.parameterList.add(it) }
|
||||||
|
|
||||||
|
val parameterList = factoryFunction.valueParameterList!!
|
||||||
|
val indexOffset = parameterList.parameters.size
|
||||||
|
val candidateClassMap = SmartHashMap<Int, Array<PsiClass>>()
|
||||||
|
val resolutionFacade = ktBeanClass.getResolutionFacade()
|
||||||
|
for ((i, psiParam) in dummyMethod.parameterList.parameters.withIndex()) {
|
||||||
|
val descriptor = psiParam.getParameterDescriptor(resolutionFacade)!!
|
||||||
|
val ktParameter = psiFactory.createParameter(
|
||||||
|
"${psiParam.name}: ${IdeDescriptorRenderers.SOURCE_CODE_NOT_NULL_TYPE_APPROXIMATION.renderType(descriptor.type)}"
|
||||||
|
)
|
||||||
|
parameterList.addParameter(ktParameter)
|
||||||
|
(psiParam.type as? PsiClassType)?.resolve()?.let { candidateClassMap[i + indexOffset] = arrayOf(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
templatesHolder.addCreateFunctionTemplate(factoryFunction, candidateClassMap, dependency)
|
||||||
|
}
|
||||||
|
catch (e: IncorrectOperationException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createConstructorDependency(
|
||||||
|
currentBean: SpringBean,
|
||||||
|
dependency: SpringBeanPointer<CommonSpringBean>
|
||||||
|
): BatchTemplateRunner? {
|
||||||
|
val templatesHolder = BatchTemplateRunner(currentBean.manager.project)
|
||||||
|
val currentBeanClass = currentBean.beanClass() as? KtLightClass ?: return null
|
||||||
|
val candidateBeanClasses = dependency.effectiveBeanClasses().ifEmpty { return null }
|
||||||
|
var existedConstructor = findExistedConstructor(currentBean, currentBeanClass, candidateBeanClasses)
|
||||||
|
if (existedConstructor == null) {
|
||||||
|
if (!ensureFileWritable(currentBeanClass)) return null
|
||||||
|
existedConstructor = findProperConstructorAndAddParameter(currentBean, dependency, currentBeanClass, candidateBeanClasses.first(), templatesHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
val newConstructorArg = currentBean.addConstructorArg()
|
||||||
|
newConstructorArg.refAttr.stringValue = getReferencedName(currentBean, dependency)
|
||||||
|
if (existedConstructor == null && SpringConstructorArgResolveUtil.findMatchingMethods(currentBean).isEmpty()) {
|
||||||
|
createConstructorWithTemplate(currentBean, dependency, templatesHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
return templatesHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateDependency(
|
||||||
|
springBean: SpringBean,
|
||||||
|
dependency: SpringBeanPointer<CommonSpringBean>,
|
||||||
|
injectionKind: SpringDependencyInjectionKind
|
||||||
|
): BatchTemplateRunner? {
|
||||||
|
return runWriteAction {
|
||||||
|
if (injectionKind.isSetter) {
|
||||||
|
createSetterDependency(springBean, dependency, injectionKind)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
createConstructorDependency(springBean, dependency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SpringDependencyInjectionKind(val isSetter: Boolean) {
|
||||||
|
CONSTRUCTOR(false),
|
||||||
|
SETTER(true),
|
||||||
|
LATEINIT_PROPERTY(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@set:TestOnly
|
||||||
|
var Project.beanFilter: (SpringBeanPointer<CommonSpringBean>) -> Boolean
|
||||||
|
by NotNullableUserDataProperty(Key.create("BEAN_CHOOSER")) { true }
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private fun chooseDependentBeans(
|
||||||
|
candidates: Set<SpringBeanClassMember>,
|
||||||
|
project: Project,
|
||||||
|
isSetterDependency: Boolean
|
||||||
|
): List<SpringBeanPointer<CommonSpringBean>> {
|
||||||
|
return if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
candidates.map { it.springBean }.filter(project.beanFilter)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GenerateSpringBeanDependenciesUtil.chooseDependentBeans(candidates, project, isSetterDependency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateDependenciesFor(
|
||||||
|
springModel: CommonSpringModel,
|
||||||
|
klass: KtLightClass,
|
||||||
|
injectionKind: SpringDependencyInjectionKind
|
||||||
|
): List<BatchTemplateRunner> {
|
||||||
|
val project = klass.project
|
||||||
|
val isSetter = injectionKind.isSetter
|
||||||
|
|
||||||
|
SpringModelSearchers
|
||||||
|
.findBeans(springModel, SpringModelSearchParameters.byClass(klass))
|
||||||
|
.asSequence()
|
||||||
|
.map { it.springBean }
|
||||||
|
.filterIsInstance<SpringBean>()
|
||||||
|
.firstOrNull { acceptBean(it, false) }
|
||||||
|
?.let { springBean ->
|
||||||
|
if (!springBean.ensureFileWritable()) return emptyList()
|
||||||
|
if (springBean.beanClass() == null) return emptyList()
|
||||||
|
val dependencies = chooseDependentBeans(getCandidates(springBean, isSetter), project, isSetter)
|
||||||
|
return dependencies.mapNotNull { generateDependency(springBean, it, injectionKind) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val dependencies = chooseDependentBeans(getCandidates(springModel, klass, isSetter), project, isSetter)
|
||||||
|
val beanXml = dependencies.singleOrNull()?.springBean as? DomSpringBean ?: return emptyList()
|
||||||
|
val beansXml = beanXml.getParentOfType(Beans::class.java, false) ?: return emptyList()
|
||||||
|
val newBean = createSpringBean(beansXml, klass) ?: return emptyList()
|
||||||
|
|
||||||
|
return dependencies.mapNotNull { generateDependency(newBean, it, injectionKind) }
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.idea.spring.inspections
|
||||||
|
|
||||||
|
import com.intellij.codeInspection.ProblemsHolder
|
||||||
|
import com.intellij.psi.PsiElementVisitor
|
||||||
|
import com.intellij.spring.model.highlighting.SpringJavaAutowiredMembersInspection
|
||||||
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
|
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
|
||||||
|
import org.jetbrains.kotlin.idea.inspections.registerWithElementsUnwrapped
|
||||||
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.KtVisitorVoid
|
||||||
|
|
||||||
|
class SpringKotlinAutowiredMembersInspection : AbstractKotlinInspection() {
|
||||||
|
private val javaInspection by lazy { SpringJavaAutowiredMembersInspection() }
|
||||||
|
|
||||||
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||||
|
return object: KtVisitorVoid() {
|
||||||
|
override fun visitClassOrObject(classOrObject: KtClassOrObject) {
|
||||||
|
val lightClass = classOrObject.toLightClass() ?: return
|
||||||
|
javaInspection.checkClass(lightClass, holder.manager, isOnTheFly)?.registerWithElementsUnwrapped(holder, isOnTheFly)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-8
@@ -16,8 +16,8 @@ import com.intellij.spring.CommonSpringModel
|
|||||||
import com.intellij.spring.SpringBundle
|
import com.intellij.spring.SpringBundle
|
||||||
import com.intellij.spring.model.SpringBeanPointer
|
import com.intellij.spring.model.SpringBeanPointer
|
||||||
import com.intellij.spring.model.converters.SpringConverterUtil
|
import com.intellij.spring.model.converters.SpringConverterUtil
|
||||||
import com.intellij.spring.model.highlighting.autowire.SpringJavaInjectionPointsAutowiringInspection
|
import com.intellij.spring.model.highlighting.SpringAutowireUtil
|
||||||
import com.intellij.spring.model.utils.SpringAutowireUtil
|
import com.intellij.spring.model.highlighting.SpringJavaAutowiringInspection
|
||||||
import org.jetbrains.kotlin.asJava.elements.KtLightField
|
import org.jetbrains.kotlin.asJava.elements.KtLightField
|
||||||
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
|
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
|
||||||
import org.jetbrains.kotlin.asJava.toLightClass
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.psi.*
|
|||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
|
|
||||||
class SpringKotlinAutowiringInspection : AbstractKotlinInspection() {
|
class SpringKotlinAutowiringInspection : AbstractKotlinInspection() {
|
||||||
// Based on SpringJavaInjectionPointsAutowiringInspection.AddSpringBeanQualifierFix
|
// Based on SpringJavaAutowiringInspection.AddSpringBeanQualifierFix
|
||||||
class AddQualifierFix(
|
class AddQualifierFix(
|
||||||
modifierListOwner: KtModifierListOwner,
|
modifierListOwner: KtModifierListOwner,
|
||||||
private val beanPointers: Collection<SpringBeanPointer<*>>,
|
private val beanPointers: Collection<SpringBeanPointer<*>>,
|
||||||
@@ -86,16 +86,16 @@ class SpringKotlinAutowiringInspection : AbstractKotlinInspection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val javaInspection by lazy { SpringJavaInjectionPointsAutowiringInspection() }
|
private val javaInspection by lazy { SpringJavaAutowiringInspection() }
|
||||||
|
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() {
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() {
|
||||||
// TODO: SpringJavaInjectionPointsAutowiringInspection.checkAutowiredMethod() is not accessible here
|
// TODO: SpringJavaAutowiringInspection.checkAutowiredMethod() is not accessible here
|
||||||
private fun checkAutowiredMethod(psiMethod: PsiMethod, holder: ProblemsHolder, springModel: CommonSpringModel, required: Boolean) {
|
private fun checkAutowiredMethod(psiMethod: PsiMethod, holder: ProblemsHolder, springModel: CommonSpringModel, required: Boolean) {
|
||||||
val resourceAnnotation = SpringAutowireUtil.getResourceAnnotation(psiMethod)
|
val resourceAnnotation = SpringAutowireUtil.getResourceAnnotation(psiMethod)
|
||||||
when {
|
when {
|
||||||
resourceAnnotation != null -> {
|
resourceAnnotation != null -> {
|
||||||
val propertyType = PropertyUtil.getPropertyType(psiMethod) ?: return
|
val propertyType = PropertyUtil.getPropertyType(psiMethod) ?: return
|
||||||
SpringJavaInjectionPointsAutowiringInspection.checkInjectionPoint(psiMethod, propertyType, holder, springModel, required)
|
SpringJavaAutowiringInspection.checkAutowiredPsiMember(psiMethod, propertyType, holder, springModel, required)
|
||||||
}
|
}
|
||||||
psiMethod.parameterList.parametersCount == 0 &&
|
psiMethod.parameterList.parametersCount == 0 &&
|
||||||
SpringAutowireUtil.isAutowiredByAnnotation(psiMethod) -> {
|
SpringAutowireUtil.isAutowiredByAnnotation(psiMethod) -> {
|
||||||
@@ -107,7 +107,7 @@ class SpringKotlinAutowiringInspection : AbstractKotlinInspection() {
|
|||||||
else -> {
|
else -> {
|
||||||
for (parameter in psiMethod.parameterList.parameters) {
|
for (parameter in psiMethod.parameterList.parameters) {
|
||||||
if (AnnotationUtil.isAnnotated(parameter, "org.springframework.beans.factory.annotation.Value", true)) continue
|
if (AnnotationUtil.isAnnotated(parameter, "org.springframework.beans.factory.annotation.Value", true)) continue
|
||||||
SpringJavaInjectionPointsAutowiringInspection.checkInjectionPoint(parameter, parameter.type, holder, springModel, required)
|
SpringJavaAutowiringInspection.checkAutowiredPsiMember(parameter, parameter.type, holder, springModel, required)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ class SpringKotlinAutowiringInspection : AbstractKotlinInspection() {
|
|||||||
private fun PsiField.processLightField() {
|
private fun PsiField.processLightField() {
|
||||||
if (!SpringAutowireUtil.isAutowiredByAnnotation(this)) return
|
if (!SpringAutowireUtil.isAutowiredByAnnotation(this)) return
|
||||||
processLightMember { holder, model, required ->
|
processLightMember { holder, model, required ->
|
||||||
SpringJavaInjectionPointsAutowiringInspection.checkInjectionPoint(this, type, holder, model, required)
|
SpringJavaAutowiringInspection.checkAutowiredPsiMember(this, type, holder, model, required)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -20,8 +20,8 @@ import com.intellij.codeInsight.lookup.LookupElement
|
|||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import com.intellij.spring.model.DefaultSpringBeanQualifier
|
import com.intellij.spring.model.DefaultSpringBeanQualifier
|
||||||
import com.intellij.spring.model.converters.SpringConverterUtil
|
import com.intellij.spring.model.converters.SpringConverterUtil
|
||||||
|
import com.intellij.spring.model.highlighting.SpringAutowireUtil
|
||||||
import com.intellij.spring.model.jam.qualifiers.SpringJamQualifier
|
import com.intellij.spring.model.jam.qualifiers.SpringJamQualifier
|
||||||
import com.intellij.spring.model.utils.SpringAutowireUtil
|
|
||||||
import com.intellij.spring.model.utils.SpringModelSearchers
|
import com.intellij.spring.model.utils.SpringModelSearchers
|
||||||
import com.intellij.spring.references.SpringQualifierReference
|
import com.intellij.spring.references.SpringQualifierReference
|
||||||
import org.jetbrains.kotlin.asJava.elements.KtLightElement
|
import org.jetbrains.kotlin.asJava.elements.KtLightElement
|
||||||
|
|||||||
Reference in New Issue
Block a user