diff --git a/ultimate/resources/META-INF/kotlin-spring.xml.172 b/ultimate/resources/META-INF/kotlin-spring.xml.172 new file mode 100644 index 00000000000..8d87a408fe4 --- /dev/null +++ b/ultimate/resources/META-INF/kotlin-spring.xml.172 @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/jam/KotlinJamReferenceContributor.kt.172 b/ultimate/src/org/jetbrains/kotlin/idea/jam/KotlinJamReferenceContributor.kt.172 new file mode 100644 index 00000000000..2860e10b445 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/jam/KotlinJamReferenceContributor.kt.172 @@ -0,0 +1,57 @@ +package org.jetbrains.kotlin.idea.jam + +import com.intellij.codeInsight.completion.CompletionUtil +import com.intellij.jam.JamService +import com.intellij.jam.JamStringAttributeElement +import com.intellij.jam.reflect.JamStringAttributeMeta +import com.intellij.psi.PsiElementRef +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceRegistrar +import com.intellij.util.SmartList +import org.jetbrains.kotlin.asJava.toLightAnnotation +import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.idea.references.AbstractKotlinReferenceContributor +import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.psi.psiUtil.isPlain +import org.jetbrains.kotlin.psi.psiUtil.parents + +// Based on the JamReferenceContributor +class KotlinJamReferenceContributor : AbstractKotlinReferenceContributor() { + @Suppress("UNCHECKED_CAST") + override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { + registrar.registerMultiProvider { expression -> + if (!expression.isPlain()) return@registerMultiProvider PsiReference.EMPTY_ARRAY + val argument = expression + .parents + .filterIsInstance() + .firstOrNull() { it.parent?.parent is KtAnnotationEntry } ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + val annotationEntry = argument.parent.parent as KtAnnotationEntry + val lightAnnotation = (CompletionUtil.getOriginalOrSelf(annotationEntry)).toLightAnnotation() + ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + + val service = JamService.getJamService(expression.project) + val annotationMeta = service.getMeta(lightAnnotation) + ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + val attributeName = argument.getArgumentName()?.asName?.asString() + val attribute = annotationMeta.findAttribute(attributeName) as? JamStringAttributeMeta + ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + val jam = attribute.getJam(PsiElementRef.real(lightAnnotation)) + val converter = attribute.converter + when (jam) { + is JamStringAttributeElement<*> -> converter.createReferences(jam as JamStringAttributeElement) + is List<*> -> { + val list = SmartList() + for (item in jam) { + val jamElement = item as? JamStringAttributeElement ?: continue + if (jamElement.psiElement?.unwrapped != expression) continue + list += converter.createReferences(jamElement) + } + list.toTypedArray() + } + else -> PsiReference.EMPTY_ARRAY + } + } + } +} diff --git a/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KotlinSpringReferenceContributor.kt.172 b/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KotlinSpringReferenceContributor.kt.172 new file mode 100644 index 00000000000..1a5c72bdc9b --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KotlinSpringReferenceContributor.kt.172 @@ -0,0 +1,157 @@ +/* + * 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.references + +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.PsiReferenceRegistrar +import com.intellij.spring.constants.SpringAnnotationsConstants +import com.intellij.spring.constants.SpringConstants +import com.intellij.spring.model.utils.resources.SpringResourcesBuilder +import com.intellij.spring.model.utils.resources.SpringResourcesUtil +import com.intellij.spring.references.SpringBeanNamesReferenceProvider +import com.intellij.spring.references.SpringBeanReference +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ConstructorDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.references.AbstractKotlinReferenceContributor +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getContentRange +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.isPlain +import org.jetbrains.kotlin.psi.psiUtil.plainContent +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.overriddenTreeAsSequence +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.resolve.source.getPsi + +// TODO: Use Kotlin patterns +class KotlinSpringReferenceContributor : AbstractKotlinReferenceContributor() { + override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { + registrar.registerProvider { + if (!it.isPlain()) return@registerProvider null + + val callExpression = (it.parent as? KtValueArgument)?.getStrictParentOfType() ?: return@registerProvider null + val context = callExpression.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = callExpression.getResolvedCall(context) ?: return@registerProvider null + val callable = resolvedCall.resultingDescriptor as? CallableMemberDescriptor ?: return@registerProvider null + if (!callable.overriddenTreeAsSequence(true).any { + it.containingDeclaration.importableFqName?.asString() == SpringConstants.BEAN_FACTORY_CLASS + }) return@registerProvider null + if (callable.name.asString() !in SpringBeanNamesReferenceProvider.METHODS) return@registerProvider null + + SpringBeanReference(it, it.getContentRange()) + } + + registrar.registerProvider(PsiReferenceRegistrar.HIGHER_PRIORITY) { + if (!it.isPlain()) return@registerProvider null + + val argument = it.parent as? KtValueArgument ?: return@registerProvider null + val argumentName = argument.getArgumentName() ?: return@registerProvider null + if (argumentName.asName.asString() != "name") return@registerProvider null + + val entry = argument.getStrictParentOfType() ?: return@registerProvider null + val context = entry.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = entry.getResolvedCall(context) ?: return@registerProvider null + val annotation = (resolvedCall.resultingDescriptor as? ConstructorDescriptor)?.containingDeclaration ?: return@registerProvider null + if (annotation.importableFqName?.asString() != SpringAnnotationsConstants.JAVAX_RESOURCE) return@registerProvider null + + val contentRange = it.getContentRange() + var startOffset = contentRange.startOffset + val isFactoryBeanRef: Boolean + if (it.plainContent.startsWith("&")) { + startOffset++ + isFactoryBeanRef = true + } + else { + isFactoryBeanRef = false + } + + val callable = (it.getStrictParentOfType()?.parent as? KtModifierList)?.parent as? KtCallableDeclaration + val callableType = (callable?.resolveToDescriptor() as? CallableDescriptor)?.returnType + val requiredSuperClass = callableType?.constructor?.declarationDescriptor?.source?.getPsi() as? KtClass + + SpringBeanReference(it, TextRange(startOffset, contentRange.endOffset), requiredSuperClass?.toLightClass(), isFactoryBeanRef) + } + + registrar.registerProvider(PsiReferenceRegistrar.HIGHER_PRIORITY) { + if (!it.isPlain()) return@registerProvider null + + val argument = it.parent as? KtValueArgument ?: return@registerProvider null + val argumentName = argument.getArgumentName() + if (argumentName != null && argumentName.asName.asString() != "value") return@registerProvider null + + val entry = argument.getStrictParentOfType() ?: return@registerProvider null + val context = entry.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = entry.getResolvedCall(context) ?: return@registerProvider null + val annotation = (resolvedCall.resultingDescriptor as? ConstructorDescriptor)?.containingDeclaration ?: return@registerProvider null + if (annotation.importableFqName?.asString() != SpringAnnotationsConstants.SCOPE) return@registerProvider null + + KtSpringBeanScopeReference(it) + } + + registrar.registerMultiProvider { + if (!it.isPlain()) return@registerMultiProvider PsiReference.EMPTY_ARRAY + + val callExpression = (it.parent as? KtValueArgument)?.getStrictParentOfType() + ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + val context = callExpression.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = callExpression.getResolvedCall(context) ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + val classDescriptor = (resolvedCall.resultingDescriptor as? ConstructorDescriptor)?.containingDeclaration + ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + val qName = classDescriptor.importableFqName?.asString() + if (qName != SpringConstants.CLASS_PATH_XML_APP_CONTEXT && qName != SpringConstants.CLASS_PATH_RESOURCE) { + return@registerMultiProvider PsiReference.EMPTY_ARRAY + } + + val content = it.plainContent + val resourcesBuilder = SpringResourcesBuilder.create(it, content).fromRoot(content.startsWith("/")).soft(false) + SpringResourcesUtil.getInstance().getClassPathReferences(resourcesBuilder) + } + + registrar.registerProvider(PsiReferenceRegistrar.HIGHER_PRIORITY) { + if (!it.isPlain()) return@registerProvider null + + val argument = it.parent as? KtValueArgument ?: return@registerProvider null + val argumentName = argument.getArgumentName() + if (argumentName != null && argumentName.asName.asString() != "value") return@registerProvider null + + val entry = argument.getStrictParentOfType() ?: return@registerProvider null + val bindingContext = entry.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = entry.getResolvedCall(bindingContext) ?: return@registerProvider null + val annotation = (resolvedCall.resultingDescriptor as? ConstructorDescriptor)?.containingDeclaration + ?: return@registerProvider null + if (annotation.importableFqName?.asString() != SpringAnnotationsConstants.QUALIFIER) return@registerProvider null + + val annotated = entry.getStrictParentOfType() ?: return@registerProvider null + if (annotated is KtClassOrObject) { + object : PsiReferenceBase(it) { + override fun resolve() = entry + override fun getVariants(): Array = arrayOf() + } + } + else { + KtSpringQualifierReference(it) + } + } + } +} \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringBeanScopeReference.kt.172 b/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringBeanScopeReference.kt.172 new file mode 100644 index 00000000000..1254d1be575 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringBeanScopeReference.kt.172 @@ -0,0 +1,44 @@ +/* + * 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.references + +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.ide.TypePresentationService +import com.intellij.psi.ElementManipulators +import com.intellij.psi.PsiReferenceBase +import com.intellij.spring.model.scope.SpringBeanScope +import com.intellij.spring.model.scope.SpringBeanScopeManager +import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.psiUtil.plainContent + +class KtSpringBeanScopeReference( + element: KtStringTemplateExpression +) : PsiReferenceBase(element, ElementManipulators.getManipulator(element).getRangeInElement(element)) { + private fun getScopes(): Sequence { + return SpringBeanScope.getDefaultScopes().asSequence() + SpringBeanScopeManager.getInstance().getCustomBeanScopes(element) + } + + private fun getLookupElement(scope: String): LookupElementBuilder { + return LookupElementBuilder + .create(scope) + .withIcon(TypePresentationService.getService().getTypeIcon(SpringBeanScope::class.java)) + } + + override fun resolve() = if (element.plainContent in getScopes().map { it.value }) element else null + + override fun getVariants() = getScopes().map { getLookupElement(it.value) }.toList().toTypedArray() +} diff --git a/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringQualifierReference.kt.172 b/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringQualifierReference.kt.172 new file mode 100644 index 00000000000..eb9e900a2cf --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringQualifierReference.kt.172 @@ -0,0 +1,127 @@ +/* + * 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.references + +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.psi.* +import com.intellij.spring.model.DefaultSpringBeanQualifier +import com.intellij.spring.model.converters.SpringConverterUtil +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.references.SpringQualifierReference +import org.jetbrains.kotlin.asJava.elements.KtLightElement +import org.jetbrains.kotlin.asJava.toLightAnnotation +import org.jetbrains.kotlin.asJava.toLightElements +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.search.allScope +import org.jetbrains.kotlin.idea.spring.springModel +import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.plainContent +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import java.util.* + +// Based on com.intellij.spring.references.SpringQualifierReference +// It inherits from SpringQualifierReference to allow reuse of existing Java inspections which explicitly check for SpringQualifierReference +class KtSpringQualifierReference(element: KtStringTemplateExpression) : SpringQualifierReference(StringTemplatePsiLiteralWrapper(element)) { + class StringTemplatePsiLiteralWrapper( + val element: KtStringTemplateExpression + ) : PsiElement by element, PsiLiteralExpression, KtLightElement, PsiCompiledElement { + + override fun getValue() = element.plainContent + override fun getType() = PsiType.getJavaLangString(element.manager, element.project.allScope()) + + override val clsDelegate: KtStringTemplateExpression + get() = element + override val kotlinOrigin: KtStringTemplateExpression + get() = element + + override fun getMirror() = null + } + + private fun getAnnotationFqName(): String? { + val annotationEntry = element.getStrictParentOfType() ?: return null + val context = annotationEntry.analyze(BodyResolveMode.PARTIAL) + val annotationType = context[BindingContext.TYPE, annotationEntry.typeReference] ?: return null + return annotationType.constructor.declarationDescriptor?.importableFqName?.asString() + } + + override fun multiResolve(incompleteCode: Boolean): Array { + val annotationEntry = element.getStrictParentOfType() ?: return ResolveResult.EMPTY_ARRAY + val lightAnnotation = annotationEntry.toLightAnnotation() ?: return ResolveResult.EMPTY_ARRAY + val springModel = element.springModel ?: return ResolveResult.EMPTY_ARRAY + + val results = ArrayList() + val jamQualifier = SpringJamQualifier(lightAnnotation, null) + + val qualifiedBeans = springModel.findQualifiedBeans(jamQualifier) + qualifiedBeans.mapTo(results) { PsiElementResolveResult(it.springBean.springQualifier!!.identifyingPsiElement) } + + val qualifierValue = jamQualifier.qualifierValue + if (qualifierValue != null) { + val beanPointer = SpringModelSearchers.findBean(springModel, qualifierValue) + val psiElement = if (beanPointer != null && beanPointer.isValid) beanPointer.psiElement else null + if (psiElement != null) results += PsiElementResolveResult(psiElement) + } + + return results.toTypedArray() + } + + // We can't get light elements for property in the synthetic completion KtFile, so look for original one instead + private fun getOriginalProperty(): KtProperty? { + val currentProperty = element.getStrictParentOfType() ?: return null + val originalFile = element.containingFile.originalFile as? KtFile ?: return null + return originalFile.findElementAt(currentProperty.startOffset)?.getNonStrictParentOfType() + } + + override fun getVariants(): Array { + val annotationFqName = getAnnotationFqName() ?: return LookupElement.EMPTY_ARRAY + val springModel = element.springModel ?: return LookupElement.EMPTY_ARRAY + val property = getOriginalProperty() ?: return LookupElement.EMPTY_ARRAY + val lightElement = property.toLightElements().firstOrNull { + it is PsiField || (it is PsiMethod && !it.name.startsWith("set")) + } + val expectedPsiType = when (lightElement) { + is PsiMethod -> lightElement.returnType + is PsiField -> lightElement.type + else -> null + } ?: return LookupElement.EMPTY_ARRAY + return SpringAutowireUtil + .autowireByType(springModel, expectedPsiType) + .mapNotNull { + val qualifier = it.springBean.springQualifier + val value = qualifier?.qualifierValue + when { + qualifier == null -> + SpringConverterUtil.createCompletionVariant(it) + value != null && (qualifier is DefaultSpringBeanQualifier || qualifier.qualifierType?.qualifiedName == annotationFqName) -> + SpringConverterUtil.createCompletionVariant(it, value) + else -> + null + } + } + .toTypedArray() + } +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.172 b/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.172 new file mode 100644 index 00000000000..41ee6c3cbd4 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.172 @@ -0,0 +1,11 @@ +// ELEMENT: java +// CHAR: \n + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("java")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.after.172 b/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.after.172 new file mode 100644 index 00000000000..2a2eadee882 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.after.172 @@ -0,0 +1,11 @@ +// ELEMENT: java +// CHAR: \n + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("javava")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.172 b/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.172 new file mode 100644 index 00000000000..db5ba7d9225 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.172 @@ -0,0 +1,11 @@ +// ELEMENT: java +// CHAR: \t + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("java")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.after.172 b/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.after.172 new file mode 100644 index 00000000000..17e1f5d7589 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.after.172 @@ -0,0 +1,11 @@ +// ELEMENT: java +// CHAR: \t + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("java")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/completion/variants/packageReference.kt.172 b/ultimate/testData/spring/core/references/completion/variants/packageReference.kt.172 new file mode 100644 index 00000000000..326f884cbb3 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/variants/packageReference.kt.172 @@ -0,0 +1,12 @@ +// NUMBER: 2 +// EXIST: { lookupString:"java" } +// EXIST: { lookupString:"javax" } + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("java")) +open class App { + +} \ No newline at end of file