diff --git a/ChangeLog.md b/ChangeLog.md
index ed80b8c9ca3..7c9d3163390 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -14,6 +14,7 @@
New features:
- [KT-11768](https://youtrack.jetbrains.com/issue/KT-11768) Implement "Introduce local variable" intention
+- [KT-11692](https://youtrack.jetbrains.com/issue/KT-11692) Support Spring model diagrams for Kotlin classes
Issues fixed:
diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt
index 95daca23580..86193f14774 100644
--- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt
+++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt
@@ -105,6 +105,10 @@ class KtLightAnnotation(
override fun getParent() = owner as? PsiElement
+ override fun delete() {
+ kotlinOrigin.delete()
+ }
+
override fun toString() = "@$qualifiedName"
override fun equals(other: Any?): Boolean {
diff --git a/ultimate/.idea/libraries/diagram_support.xml b/ultimate/.idea/libraries/diagram_support.xml
new file mode 100644
index 00000000000..c7a278bcafe
--- /dev/null
+++ b/ultimate/.idea/libraries/diagram_support.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ultimate/kotlin-ultimate.iml b/ultimate/kotlin-ultimate.iml
index 398bda02821..bf2f530e0b6 100644
--- a/ultimate/kotlin-ultimate.iml
+++ b/ultimate/kotlin-ultimate.iml
@@ -25,6 +25,7 @@
+
diff --git a/ultimate/src/META-INF/kotlin-spring.xml b/ultimate/src/META-INF/kotlin-spring.xml
index fe33323ba4b..7ef9c4a492c 100644
--- a/ultimate/src/META-INF/kotlin-spring.xml
+++ b/ultimate/src/META-INF/kotlin-spring.xml
@@ -5,6 +5,7 @@
+
com.intellij.database
com.intellij.css
com.intellij.jsp
+ com.intellij.diagram
\ No newline at end of file
diff --git a/ultimate/src/org/jetbrains/kotlin/idea/spring/diagram/KotlinSpringLocalModelDependenciesDiagramProvider.kt b/ultimate/src/org/jetbrains/kotlin/idea/spring/diagram/KotlinSpringLocalModelDependenciesDiagramProvider.kt
new file mode 100644
index 00000000000..c7c877b0ae0
--- /dev/null
+++ b/ultimate/src/org/jetbrains/kotlin/idea/spring/diagram/KotlinSpringLocalModelDependenciesDiagramProvider.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.diagram
+
+import com.intellij.diagram.DiagramPresentationModel
+import com.intellij.diagram.presentation.DiagramState
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.roots.ProjectRootManager
+import com.intellij.openapi.util.component1
+import com.intellij.openapi.util.component2
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.spring.SpringManager
+import com.intellij.spring.contexts.model.AbstractSimpleLocalModel
+import com.intellij.spring.contexts.model.LocalModel
+import com.intellij.spring.contexts.model.diagram.SpringLocalModelDependenciesDiagramProvider
+import com.intellij.spring.contexts.model.diagram.SpringLocalModelDiagramNodeContentManager
+import com.intellij.spring.contexts.model.diagram.SpringLocalModelsDataModel
+import com.intellij.spring.contexts.model.diagram.beans.*
+import com.intellij.spring.contexts.model.graph.LazyModelDependenciesGraph
+import com.intellij.spring.contexts.model.graph.LocalModelDependency
+import com.intellij.spring.facet.SpringFacet
+import com.intellij.spring.facet.SpringFileSet
+import com.intellij.spring.facet.SpringFileSetService
+import org.jetbrains.kotlin.idea.refactoring.toPsiFile
+import org.jetbrains.kotlin.psi.KtFile
+import java.util.*
+
+// TODO: Drop when SpringLocalModelsDataModel gets support for non-Java/non-XML files
+class KotlinSpringLocalModelDependenciesDiagramProvider : SpringLocalModelDependenciesDiagramProvider() {
+ inner class KotlinSpringLocalModelsDataModel(
+ project: Project,
+ private val model: LocalModelGraphElementWrapper<*>,
+ private val showModuleFilesetNodes: Boolean
+ ) : SpringLocalModelsDataModel(project, this@KotlinSpringLocalModelDependenciesDiagramProvider, model, showModuleFilesetNodes) {
+ private val extraNodes = LinkedHashSet()
+ private val extraEdges = LinkedHashSet()
+
+ @Suppress("UNCHECKED_CAST")
+ private val nodesSequence: Sequence
+ get() = (super.getNodes() as Collection).asSequence() + extraNodes.asSequence()
+
+ private fun getDiagramNode(model: LocalModelGraphElementWrapper<*>): SpringLocalModelDiagramNode {
+ return nodesSequence.firstOrNull { it.identifyingElement == model } ?: SpringLocalModelDiagramNode(model, this.provider)
+ }
+
+ private fun showLibraryConfigs() = DiagramState(builder).isCategoryEnabled(SpringLocalModelDiagramNodeContentManager.SHOW_LIBRARY_CONFIGS)
+
+ private fun isLibraryConfig(element: LocalModelGraphElementWrapper): Boolean {
+ if (element !is LocalModelWrapper<*>) return false
+ return ProjectRootManager.getInstance(project).fileIndex.isInLibraryClasses(element.element.config.containingFile.virtualFile)
+ }
+
+ private fun visitRelated(fromNode: SpringLocalModelDiagramNode, graph: LazyModelDependenciesGraph) {
+ val localModel = (fromNode.identifyingElement as? LocalModelWrapper<*>)?.element ?: return
+ for ((model, dependency) in graph.getOrCreateOutDependencies(localModel)) {
+ createEdgeToLocalModelAndVisitIt(fromNode, graph, LocalModelWrapper.create(model), dependency)
+ }
+ }
+
+ private fun createEdgeToLocalModelAndVisitIt(
+ fromNode: SpringLocalModelDiagramNode?,
+ graph: LazyModelDependenciesGraph,
+ toNodeLocalModelWrapper: LocalModelWrapper>,
+ dependency: LocalModelDependency
+ ) {
+ val toNode = getDiagramNode(toNodeLocalModelWrapper)
+ if (toNode === fromNode) return
+
+ val toNodeExists = toNode in extraNodes
+ if (!toNodeExists) {
+ extraNodes += toNode
+ if (showLibraryConfigs() || !isLibraryConfig(toNode.identifyingElement)) {
+ this.visitRelated(toNode, graph)
+ }
+ }
+
+ if (fromNode != null) {
+ val edge = SpringLocalModelDependencyEdge(fromNode, toNode, dependency)
+ if (toNodeExists) {
+ edge.isError = true
+ }
+ else {
+ fromNode.addChild(toNode)
+ }
+ extraEdges += edge
+ }
+ }
+
+ private fun visitFileSet(node: SpringLocalModelDiagramNode?, fileSet: SpringFileSet) {
+ val springManager = SpringManager.getInstance(project)
+ val module = fileSet.facet.module
+ val profiles = fileSet.activeProfiles
+ val graph = AbstractSimpleLocalModel.getOrCreateLocalModelDependenciesGraph(module, profiles)
+
+ for (filePointer in fileSet.files) {
+ val virtualFile = filePointer.file ?: continue
+ val ktFile = virtualFile.toPsiFile(project) as? KtFile ?: continue
+ for (psiClass in ktFile.classes) {
+ val model = springManager.getLocalSpringModel(psiClass, module) ?: continue
+ createEdgeToLocalModelAndVisitIt(node, graph, LocalModelWrapper.create(model), LocalModelDependency.create())
+ }
+ }
+ }
+
+ private fun visitFileSet(fileSet: SpringFileSet) {
+ visitFileSet(if (showModuleFilesetNodes) getDiagramNode(FilesetLocalModelWrapper.create(fileSet)!!) else null, fileSet)
+ }
+
+ private fun visitModule(module: Module) {
+ if (module.isDisposed) return
+ val springFacet = SpringFacet.getInstance(module) ?: return
+
+ for (fileSet in SpringFileSetService.getInstance().getAllSets(springFacet)) {
+ visitFileSet(fileSet)
+ }
+ }
+
+ override fun updateDataModel() {
+ super.updateDataModel()
+ when (model) {
+ is ModuleLocalModelWrapper -> visitModule(model.element)
+ is FilesetLocalModelWrapper -> visitFileSet(model.element)
+ }
+ }
+
+ override fun getNodes() = nodesSequence.toCollection(LinkedHashSet())
+ override fun getEdges() = super.getEdges() + extraEdges
+ }
+
+ override fun getID() = "KotlinSpringLocalModels"
+
+ override fun createDataModel(
+ project: Project,
+ element: LocalModelGraphElementWrapper<*>?,
+ file: VirtualFile?,
+ presentationModel: DiagramPresentationModel?
+ ): SpringLocalModelsDataModel? {
+ if (element == null) return null
+ return KotlinSpringLocalModelsDataModel(project, element, true)
+ }
+}
diff --git a/ultimate/src/org/jetbrains/kotlin/idea/spring/diagram/OpenKotlinSpringModelDependenciesAction.kt b/ultimate/src/org/jetbrains/kotlin/idea/spring/diagram/OpenKotlinSpringModelDependenciesAction.kt
new file mode 100644
index 00000000000..929fc4aeabc
--- /dev/null
+++ b/ultimate/src/org/jetbrains/kotlin/idea/spring/diagram/OpenKotlinSpringModelDependenciesAction.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.diagram
+
+import com.intellij.diagram.DiagramProvider
+import com.intellij.openapi.actionSystem.CommonDataKeys
+import com.intellij.openapi.actionSystem.DataContext
+import com.intellij.spring.contexts.model.diagram.actions.OpenSpringModelDependenciesAction
+import org.jetbrains.kotlin.asJava.toLightClass
+import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
+
+class OpenKotlinSpringModelDependenciesAction(klass: KtClass) : OpenSpringModelDependenciesAction() {
+ private val klassPointer = klass.createSmartPointer()
+
+ override fun findInDataContext(provider: DiagramProvider<*>, context: DataContext): Any? {
+ val wrappingContext = DataContext { id ->
+ if (CommonDataKeys.PSI_ELEMENT.`is`(id)) klassPointer.element?.toLightClass() else context.getData(id)
+ }
+ return super.findInDataContext(provider, wrappingContext)
+ }
+}
diff --git a/ultimate/src/org/jetbrains/kotlin/idea/spring/diagram/OpenKotlinSpringModelDependenciesModuleDiagramAction.kt b/ultimate/src/org/jetbrains/kotlin/idea/spring/diagram/OpenKotlinSpringModelDependenciesModuleDiagramAction.kt
new file mode 100644
index 00000000000..4b726c7fe80
--- /dev/null
+++ b/ultimate/src/org/jetbrains/kotlin/idea/spring/diagram/OpenKotlinSpringModelDependenciesModuleDiagramAction.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.diagram
+
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.spring.contexts.model.diagram.actions.OpenSpringModelDependenciesModuleDiagramAction
+
+// TODO: Drop when SpringLocalModelsDataModel gets support for non-Java/non-XML files
+class OpenKotlinSpringModelDependenciesModuleDiagramAction : OpenSpringModelDependenciesModuleDiagramAction() {
+ override fun getProvider(e: AnActionEvent) = KotlinSpringLocalModelDependenciesDiagramProvider()
+}
\ No newline at end of file
diff --git a/ultimate/src/org/jetbrains/kotlin/idea/spring/lineMarking/KotlinSpringModelDependenciesLineMarkerProvider.kt b/ultimate/src/org/jetbrains/kotlin/idea/spring/lineMarking/KotlinSpringModelDependenciesLineMarkerProvider.kt
new file mode 100644
index 00000000000..ee5838e0ddb
--- /dev/null
+++ b/ultimate/src/org/jetbrains/kotlin/idea/spring/lineMarking/KotlinSpringModelDependenciesLineMarkerProvider.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.lineMarking
+
+import com.intellij.codeHighlighting.Pass
+import com.intellij.codeInsight.daemon.GutterIconNavigationHandler
+import com.intellij.codeInsight.daemon.LineMarkerInfo
+import com.intellij.openapi.actionSystem.DefaultActionGroup
+import com.intellij.openapi.actionSystem.impl.SimpleDataContext
+import com.intellij.openapi.editor.markup.GutterIconRenderer
+import com.intellij.openapi.ui.popup.JBPopupFactory
+import com.intellij.psi.PsiElement
+import com.intellij.spring.SpringBundle
+import com.intellij.spring.SpringManager
+import com.intellij.spring.contexts.model.diagram.gutter.ModelDependenciesLineMarkerProviderJava
+import com.intellij.spring.model.jam.stereotype.SpringConfiguration
+import com.intellij.spring.model.utils.SpringCommonUtils
+import com.intellij.ui.awt.RelativePoint
+import com.intellij.util.Function
+import icons.SpringCoreIcons
+import org.jetbrains.kotlin.asJava.toLightClass
+import org.jetbrains.kotlin.idea.spring.diagram.OpenKotlinSpringModelDependenciesAction
+import org.jetbrains.kotlin.idea.spring.diagram.OpenKotlinSpringModelDependenciesModuleDiagramAction
+import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
+
+class KotlinSpringModelDependenciesLineMarkerProvider : ModelDependenciesLineMarkerProviderJava() {
+ override fun getId() = javaClass.name
+
+ override fun getName() = "Model Dependencies Graph (Kotlin)"
+
+ override fun isRelevantPsiElement(psiElement: PsiElement): Boolean {
+ val lightClass = psiElement.getParentOfTypeAndBranch { nameIdentifier }?.toLightClass() ?: return false
+ return SpringConfiguration.PSI_CLASS_PATTERN.accepts(lightClass) && SpringCommonUtils.isConfigurationOrMeta(lightClass)
+ }
+
+ override fun doAnnotate(element: PsiElement): LineMarkerInfo<*>? {
+ val klass = element.getParentOfTypeAndBranch { nameIdentifier } ?: return null
+ val lightClass = klass.toLightClass() ?: return null
+ if (SpringManager.getInstance(element.project).getLocalSpringModel(lightClass) == null) return null
+ return createLineMarkerInfo(klass)
+ }
+
+ private fun createLineMarkerInfo(klass: KtClass): LineMarkerInfo? {
+ val nameIdentifier = klass.nameIdentifier ?: return null
+ return LineMarkerInfo(
+ nameIdentifier,
+ nameIdentifier.textRange,
+ SpringCoreIcons.SpringModelsDependencyGraph,
+ Pass.UPDATE_ALL,
+ Function { SpringBundle.message("local.model.dependencies.diagram.title") },
+ createNavigationHandler(klass),
+ GutterIconRenderer.Alignment.RIGHT
+ )
+ }
+
+ private fun createNavigationHandler(klass: KtClass): GutterIconNavigationHandler {
+ return GutterIconNavigationHandler { event, element ->
+ val group = DefaultActionGroup().apply {
+ add(OpenKotlinSpringModelDependenciesModuleDiagramAction())
+ add(OpenKotlinSpringModelDependenciesAction(klass))
+ }
+ val context = SimpleDataContext.getProjectContext(null)
+ JBPopupFactory.getInstance()
+ .createActionGroupPopup(null, group, context, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true)
+ .show(RelativePoint(event))
+ }
+ }
+}
\ No newline at end of file