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