Spring Support: "Generate Dependency..." actions

#KT-11407 Fixed
This commit is contained in:
Alexey Sedunov
2016-03-04 13:13:07 +03:00
parent 4ed1c3d448
commit 233f2b468d
45 changed files with 1163 additions and 8 deletions
+1
View File
@@ -129,6 +129,7 @@ New features:
- [KT-3741](https://youtrack.jetbrains.com/issue/KT-3741) Show Spring-specific line markers on Kotlin classes
- [KT-11406](https://youtrack.jetbrains.com/issue/KT-11406) Support Spring EL injections inside of Kotlin string literals
- [KT-11604](https://youtrack.jetbrains.com/issue/KT-11604) Support "Configure Spring facet" inspection on Kotlin classes
- [KT-11407](https://youtrack.jetbrains.com/issue/KT-11407) Implemented "Generate Spring Dependency..." actions
Issues fixed:
@@ -99,6 +99,7 @@ import org.jetbrains.kotlin.idea.refactoring.pushDown.AbstractPushDownTest
import org.jetbrains.kotlin.idea.refactoring.rename.AbstractRenameTest
import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractSafeDeleteTest
import org.jetbrains.kotlin.idea.resolve.*
import org.jetbrains.kotlin.idea.spring.tests.generate.AbstractGenerateSpringDependencyActionTest
import org.jetbrains.kotlin.idea.spring.tests.references.AbstractSpringReferenceCompletionHandlerTest
import org.jetbrains.kotlin.idea.spring.tests.references.AbstractSpringReferenceCompletionTest
import org.jetbrains.kotlin.idea.spring.tests.references.AbstractSpringReferenceNavigationTest
@@ -814,6 +815,10 @@ fun main(args: Array<String>) {
testClass<AbstractSpringReferenceNavigationTest>() {
model("spring/core/references/navigation")
}
testClass<AbstractGenerateSpringDependencyActionTest>() {
model("spring/core/generate", pattern = "^([\\w]+)\\.kt$", singleClass = true)
}
}
testGroup("idea/tests", "compiler/testData") {
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.load.java.structure.impl.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.scopes.MemberScope
@@ -82,6 +83,12 @@ fun PsiMember.getJavaOrKotlinMemberDescriptor(resolutionFacade: ResolutionFacade
}
}
fun PsiParameter.getParameterDescriptor(resolutionFacade: ResolutionFacade? = null): ValueParameterDescriptor? {
val method = declarationScope as? PsiMethod ?: return null
val methodDescriptor = method.getJavaMethodDescriptor(resolutionFacade) ?: return null
return methodDescriptor.valueParameters[parameterIndex()]
}
fun PsiClass.resolveToDescriptor(
resolutionFacade: ResolutionFacade,
declarationTranslator: (KtClassOrObject) -> KtClassOrObject? = { it }
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.core
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
@@ -35,6 +36,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
@@ -60,14 +62,18 @@ class CollectingNameValidator @JvmOverloads constructor(
class NewDeclarationNameValidator(
private val visibleDeclarationsContext: KtElement?,
private val checkDeclarationsIn: Sequence<PsiElement>,
private val target: NewDeclarationNameValidator.Target
private val target: NewDeclarationNameValidator.Target,
private val excludedDeclarations: List<KtDeclaration> = emptyList()
) : (String) -> Boolean {
constructor(container: PsiElement, anchor: PsiElement?, target: NewDeclarationNameValidator.Target)
constructor(container: PsiElement,
anchor: PsiElement?,
target: NewDeclarationNameValidator.Target,
excludedDeclarations: List<KtDeclaration> = emptyList())
: this(
(anchor ?: container).parentsWithSelf.firstIsInstanceOrNull<KtElement>(),
anchor?.siblings() ?: container.allChildren,
target)
target,
excludedDeclarations)
enum class Target {
VARIABLES,
@@ -88,6 +94,8 @@ class NewDeclarationNameValidator(
}
}
private fun notExcluded(it: DeclarationDescriptorWithSource) = it.source.getPsi() !in excludedDeclarations
private fun LexicalScope.hasConflict(name: Name): Boolean {
fun DeclarationDescriptor.isVisible(): Boolean {
return when (this) {
@@ -98,14 +106,15 @@ class NewDeclarationNameValidator(
return when(target) {
Target.VARIABLES ->
getAllAccessibleVariables(name).any { !it.isExtension && it.isVisible() }
getAllAccessibleVariables(name).any { !it.isExtension && it.isVisible() && notExcluded(it) }
Target.FUNCTIONS_AND_CLASSES ->
getAllAccessibleFunctions(name).any { !it.isExtension && it.isVisible() } ||
findClassifier(name, NoLookupLocation.FROM_IDE)?.let { it.isVisible() } ?: false
getAllAccessibleFunctions(name).any { !it.isExtension && it.isVisible() && notExcluded(it) } ||
findClassifier(name, NoLookupLocation.FROM_IDE)?.let { it.isVisible() && notExcluded(it) } ?: false
}
}
private fun KtNamedDeclaration.isConflicting(name: Name): Boolean {
if (this in excludedDeclarations) return false
if (nameAsName != name) return false
if (this is KtCallableDeclaration && receiverTypeReference != null) return false
return when(target) {
@@ -22,4 +22,19 @@
level="WARNING"
implementationClass="org.jetbrains.kotlin.idea.spring.inspections.KotlinSpringFacetCodeInspection"/>
</extensions>
<actions>
<action id="Kotlin.Spring.Beans.Generate.Setter.Dependency.Action"
class="org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Setter">
<add-to-group anchor="last" group-id="GenerateGroup"/>
</action>
<action id="Kotlin.Spring.Beans.Generate.Constructor.Dependency.Action"
class="org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor">
<add-to-group anchor="last" group-id="GenerateGroup"/>
</action>
<action id="Kotlin.Spring.Beans.Generate.Lateinit.Property.Dependency.Action"
class="org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$LateinitProperty">
<add-to-group anchor="last" group-id="GenerateGroup"/>
</action>
</actions>
</idea-plugin>
@@ -0,0 +1,73 @@
/*
* 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.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.xml.XmlFile
import com.intellij.spring.SpringBundle
import com.intellij.spring.SpringManager
import com.intellij.spring.model.actions.generate.GenerateSpringBeanDependenciesUtil
import com.intellij.spring.model.utils.SpringModelUtils
import icons.SpringApiIcons
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateActionBase
import org.jetbrains.kotlin.idea.editor.BatchTemplateRunner
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
abstract class GenerateKotlinSpringBeanDependencyAction(
text: String,
private val injectionKind: SpringDependencyInjectionKind
) : KotlinGenerateActionBase() {
class Constructor: GenerateKotlinSpringBeanDependencyAction(
SpringBundle.message("action.Spring.Beans.Generate.Constructor.Dependency.Action.text"),
SpringDependencyInjectionKind.CONSTRUCTOR
)
class Setter: GenerateKotlinSpringBeanDependencyAction(
SpringBundle.message("action.Spring.Beans.Generate.Setter.Dependency.Action.text"),
SpringDependencyInjectionKind.SETTER
)
class LateinitProperty: GenerateKotlinSpringBeanDependencyAction(
"Spring 'lateinit' Dependency...",
SpringDependencyInjectionKind.LATEINIT_PROPERTY
)
init {
templatePresentation.text = text
templatePresentation.icon = SpringApiIcons.Spring
}
override fun isValidForClass(targetClass: KtClassOrObject): Boolean {
if (targetClass !is KtClass || targetClass.isInterface() || targetClass.isEnum() || targetClass.fqName == null) return false
val lightClass = targetClass.toLightClass() ?: return false
val module = GenerateSpringBeanDependenciesUtil.getSpringModule(lightClass) ?: return false
return SpringManager.getInstance(module.project).getCombinedModel(module).configFiles.any { it is XmlFile }
}
override fun invoke(project: Project, editor: Editor, file: PsiFile) {
val klass = getTargetClass(editor, file) as? KtClass ?: return
val lightClass = klass.toLightClass() ?: return
val springModel = SpringModelUtils.getInstance().getPsiClassSpringModel(lightClass)
val templates = project.executeWriteCommand<List<BatchTemplateRunner>>("") {
generateDependenciesFor(springModel, lightClass, injectionKind)
}
templates.forEach { it.runTemplates() }
}
}
@@ -0,0 +1,497 @@
/*
* 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.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.*
import com.intellij.psi.codeStyle.CodeStyleManager
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.KtLightClass
import org.jetbrains.kotlin.asJava.KtLightMethod
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.*
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.getOrCreateCompanionObject
import org.jetbrains.kotlin.idea.editor.BatchTemplateRunner
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.load.java.propertyNameBySetMethodName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
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 }
}
private 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 && KotlinNameSuggester.isIdentifier(beanName)) 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)
}
}
private fun getSuggestedNames(
beanPointer: SpringBeanPointer<CommonSpringBean>,
declaration: KtCallableDeclaration,
getType: CallableDescriptor.() -> KotlinType?
): Collection<String> {
val names = LinkedHashSet<String>()
val validator = NewDeclarationNameValidator(declaration.parent, null, NewDeclarationNameValidator.Target.VARIABLES, listOf(declaration))
SpringBeanUtils.getInstance()
.findBeanNames(beanPointer.springBean)
.asSequence()
.filter { KotlinNameSuggester.isIdentifier(it) }
.mapTo(names) { KotlinNameSuggester.suggestNameByName(it, validator) }
(declaration.resolveToDescriptor() as CallableDescriptor).getType()?.let {
names += KotlinNameSuggester.suggestNamesByType(it, validator)
}
return names
}
private 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) { 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.effectiveBeanType.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.effectiveBeanType.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 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) }
}
@@ -0,0 +1,68 @@
/*
* 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.tests.generate
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.PathUtil
import junit.framework.TestCase
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractCodeInsightActionTest
import org.jetbrains.kotlin.idea.spring.generate.beanFilter
import org.jetbrains.kotlin.idea.spring.tests.SpringTestFixtureExtension
import org.jetbrains.kotlin.idea.test.TestFixtureExtension
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import java.io.File
abstract class AbstractGenerateSpringDependencyActionTest : AbstractCodeInsightActionTest() {
private var configFile: VirtualFile? = null
private var configFilePath: String? = null
override fun setUp() {
super.setUp()
TestFixtureExtension.loadFixture<SpringTestFixtureExtension>(myModule)
}
override fun configureExtra(mainFilePath: String, mainFileText: String) {
configFilePath = InTextDirectivesUtils.findStringWithPrefixes(mainFileText, "// CONFIG_FILE: ")?.let {
"${PathUtil.getParentPath(mainFilePath)}/$it"
} ?: mainFilePath
val springFileSet = TestFixtureExtension
.getFixture<SpringTestFixtureExtension>()!!
.configureFileSet(myFixture, listOf(configFilePath!!))
if (configFilePath != mainFilePath) {
configFile = springFileSet.files.single().file!!
}
val beansToChoose = InTextDirectivesUtils.findListWithPrefixes(mainFileText, "// CHOOSE_BEAN: ")
project.beanFilter = { it.name in beansToChoose }
}
override fun checkExtra() {
configFile?.let {
myFixture.openFileInEditor(it)
val afterFile = File("$configFilePath.after")
TestCase.assertTrue(afterFile.exists())
myFixture.checkResult(FileUtil.loadFile(afterFile, true))
}
}
override fun tearDown() {
configFile = null
TestFixtureExtension.unloadFixture<SpringTestFixtureExtension>()
super.tearDown()
}
}
@@ -0,0 +1,67 @@
/*
* 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.tests.generate;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/spring/core/generate")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class GenerateSpringDependencyActionTestGenerated extends AbstractGenerateSpringDependencyActionTest {
public void testAllFilesPresentInGenerate() throws Exception {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/spring/core/generate"), Pattern.compile("^([\\w]+)\\.kt$"));
}
@TestMetadata("beanDependenciesByXml/firstConstructor.kt")
public void testBeanDependenciesByXml_FirstConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/spring/core/generate/beanDependenciesByXml/firstConstructor.kt");
doTest(fileName);
}
@TestMetadata("beanDependenciesByXml/primaryConstructorAddParam.kt")
public void testBeanDependenciesByXml_PrimaryConstructorAddParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/spring/core/generate/beanDependenciesByXml/primaryConstructorAddParam.kt");
doTest(fileName);
}
@TestMetadata("beanDependenciesByXml/property.kt")
public void testBeanDependenciesByXml_Property() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/spring/core/generate/beanDependenciesByXml/property.kt");
doTest(fileName);
}
@TestMetadata("beanDependenciesByXml/secondaryConstructorAddParam.kt")
public void testBeanDependenciesByXml_SecondaryConstructorAddParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/spring/core/generate/beanDependenciesByXml/secondaryConstructorAddParam.kt");
doTest(fileName);
}
@TestMetadata("beanDependenciesByXml/setter.kt")
public void testBeanDependenciesByXml_Setter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/spring/core/generate/beanDependenciesByXml/setter.kt");
doTest(fileName);
}
}
@@ -0,0 +1,74 @@
/*
* 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.editor
import com.intellij.codeInsight.template.Template
import com.intellij.codeInsight.template.TemplateEditingAdapter
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.OpenFileDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.SmartPsiElementPointer
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import java.util.*
class BatchTemplateRunner(private val project: Project) {
private val elementsAndFactories = ArrayList<Pair<SmartPsiElementPointer<*>, () -> Template?>>()
fun addTemplateFactory(element: PsiElement, factory: () -> Template?) {
elementsAndFactories.add(element.createSmartPointer() to factory)
}
fun runTemplates() {
runTemplates(elementsAndFactories.iterator())
}
private fun getEditor(pointer: SmartPsiElementPointer<*>): Editor? {
val element = pointer.element ?: return null
val virtualFile = element.containingFile?.virtualFile ?: return null
val descriptor = OpenFileDescriptor(project, virtualFile, element.textRange.startOffset)
return FileEditorManager.getInstance(project).openTextEditor(descriptor, true)
}
private fun runTemplates(iterator: Iterator<Pair<SmartPsiElementPointer<*>, () -> Template?>>) {
if (!iterator.hasNext()) return
val manager = TemplateManager.getInstance(project)
project.executeWriteCommand("") {
val (pointer, factory) = iterator.next()
val editor = getEditor(pointer) ?: return@executeWriteCommand
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document)
val template = factory() ?: return@executeWriteCommand
manager.startTemplate(
editor,
template,
object : TemplateEditingAdapter() {
override fun templateFinished(template: Template?, brokenOff: Boolean) {
if (brokenOff) return
ApplicationManager.getApplication().invokeLater { runTemplates(iterator) }
}
}
)
}
}
}
@@ -118,6 +118,7 @@ fun createSpacingBuilder(settings: CodeStyleSettings): KotlinSpacingBuilder {
between(ENUM_ENTRY, SEMICOLON).spaces(0)
beforeInside(FUN, TokenSet.create(BODY, CLASS_BODY)).lineBreakInCode()
beforeInside(SECONDARY_CONSTRUCTOR, TokenSet.create(BODY, CLASS_BODY)).lineBreakInCode()
beforeInside(CLASS, TokenSet.create(BODY, CLASS_BODY)).lineBreakInCode()
beforeInside(OBJECT_DECLARATION, TokenSet.create(BODY, CLASS_BODY)).lineBreakInCode()
before(PROPERTY).lineBreakInCode()
@@ -64,6 +64,7 @@ import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Collections;
import java.util.List;
public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
@@ -202,7 +203,8 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
Function1<String, Boolean> validator =
innerClassBody != null
? new NewDeclarationNameValidator(innerClassBody, (PsiElement) null,
NewDeclarationNameValidator.Target.VARIABLES)
NewDeclarationNameValidator.Target.VARIABLES,
Collections.<KtDeclaration>emptyList())
: new CollectingNameValidator();
List<String> suggestions = KotlinNameSuggester.INSTANCE.suggestNamesByType(getOuterInstanceType(), validator, "outer");
parameterField.setSuggestions(ArrayUtil.toStringArray(suggestions));
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean" factory-bean="fooFactoryBean" factory-method="create"/>
<bean id="fooFactoryBean" class="a.FooFactoryBean"/>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean" factory-bean="fooFactoryBean" factory-method="create">
<constructor-arg ref="barBean"/>
</bean>
<bean id="fooFactoryBean" class="a.FooFactoryBean"/>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,13 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: factoryMethod-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean {<caret>
}
open class FooFactoryBean {
open fun create(barBean: BarBean) = FooBean()
}
open class BarBean
@@ -0,0 +1,13 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: factoryMethod-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean {<caret>
}
open class FooFactoryBean {
open fun create() = FooBean()
}
open class BarBean
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean" factory-method="create" />
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean" factory-method="create">
<constructor-arg ref="barBean"/>
</bean>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,14 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: factoryMethod-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean {
companion object {
@JvmStatic fun create(barBean: BarBean): FooBean {
return null
}
}
}
open class BarBean
@@ -0,0 +1,9 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: factoryMethod-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean {<caret>
}
open class BarBean
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean"/>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean">
<constructor-arg ref="barBean"/>
</bean>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,9 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: firstConstructor-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean {<caret>
}
open class BarBean
@@ -0,0 +1,9 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: firstConstructor-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean(barBean: BarBean) {
}
open class BarBean
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean">
<constructor-arg ref="barBean"/>
</bean>
<bean id="barBean" class="a.BarBean"/>
<bean id="bazBean" class="a.BazBean"/>
</beans>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean">
<constructor-arg ref="barBean"/>
<constructor-arg ref="bazBean"/>
</bean>
<bean id="barBean" class="a.BarBean"/>
<bean id="bazBean" class="a.BazBean"/>
</beans>
@@ -0,0 +1,11 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: primaryConstructorAddParam-config.xml
// CHOOSE_BEAN: bazBean
package a
open class FooBean(barBean: BarBean) {<caret>
}
open class BarBean
open class BazBean
@@ -0,0 +1,11 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: primaryConstructorAddParam-config.xml
// CHOOSE_BEAN: bazBean
package a
open class FooBean(barBean: BarBean, bazBean: BazBean) {
}
open class BarBean
open class BazBean
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean"/>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean">
<property name="barBean" ref="barBean"/>
</bean>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,9 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$LateinitProperty
// CONFIG_FILE: property-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean {<caret>
}
open class BarBean
@@ -0,0 +1,10 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$LateinitProperty
// CONFIG_FILE: property-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean {
lateinit var barBean: BarBean
}
open class BarBean
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean"/>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean"/>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,10 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: secondaryConstructor-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean(n: Int) {
<caret>
}
open class BarBean
@@ -0,0 +1,9 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: secondaryConstructor-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean(n: Int) {<caret>
}
open class BarBean
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean">
<constructor-arg ref="barBean"/>
</bean>
<bean id="barBean" class="a.BarBean"/>
<bean id="bazBean" class="a.BazBean"/>
</beans>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean">
<constructor-arg ref="barBean"/>
<constructor-arg ref="bazBean"/>
</bean>
<bean id="barBean" class="a.BarBean"/>
<bean id="bazBean" class="a.BazBean"/>
</beans>
@@ -0,0 +1,12 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: secondaryConstructorAddParam-config.xml
// CHOOSE_BEAN: bazBean
package a
open class FooBean(n: Int) {
constructor(barBean: BarBean): this(1)<caret>
}
open class BarBean
open class BazBean
@@ -0,0 +1,12 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Constructor
// CONFIG_FILE: secondaryConstructorAddParam-config.xml
// CHOOSE_BEAN: bazBean
package a
open class FooBean(n: Int) {
constructor(barBean: BarBean, bazBean: BazBean): this(1)
}
open class BarBean
open class BazBean
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean"/>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-init-method="myDefaultInitMethod"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fooBean" class="a.FooBean">
<property name="barBean" ref="barBean"/>
</bean>
<bean id="barBean" class="a.BarBean"/>
</beans>
@@ -0,0 +1,9 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Setter
// CONFIG_FILE: setter-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean {<caret>
}
open class BarBean
@@ -0,0 +1,11 @@
// ACTION_CLASS: org.jetbrains.kotlin.idea.spring.generate.GenerateKotlinSpringBeanDependencyAction$Setter
// CONFIG_FILE: setter-config.xml
// CHOOSE_BEAN: barBean
package a
open class FooBean {
fun setBarBean(barBean: BarBean) {
}
}
open class BarBean
@@ -37,6 +37,14 @@ abstract class AbstractCodeInsightActionTest : KotlinLightCodeInsightFixtureTest
return Class.forName(actionClassName).newInstance() as CodeInsightAction
}
protected open fun configureExtra(mainFilePath: String, mainFileText: String) {
}
protected open fun checkExtra() {
}
protected open fun testAction(action: AnAction, forced: Boolean): Presentation {
val e = TestActionEvent(action)
action.beforeActionPerformedUpdate(e)
@@ -65,6 +73,7 @@ abstract class AbstractCodeInsightActionTest : KotlinLightCodeInsightFixtureTest
.forEach {
myFixture.configureByFile(File(rootDir, it).path.replace(File.separator, "/"))
}
configureExtra(path, fileText)
myFixture.configureByFile(path)
val action = createAction(fileText)
@@ -83,6 +92,8 @@ abstract class AbstractCodeInsightActionTest : KotlinLightCodeInsightFixtureTest
val afterFile = File("$path.after")
TestCase.assertTrue(afterFile.exists())
myFixture.checkResult(FileUtil.loadFile(afterFile, true))
checkExtra()
}
}
catch (e: CommonRefactoringUtil.RefactoringErrorHintException) {