Spring Support: Model diagram support for Kotlin classes
#KT-11692 In Progress
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="diagram-support">
|
||||
<CLASSES>
|
||||
<root url="file://$PROJECT_DIR$/ideaSDK/plugins/uml/lib" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
<jarDirectory url="file://$PROJECT_DIR$/ideaSDK/plugins/uml/lib" recursive="false" />
|
||||
</library>
|
||||
</component>
|
||||
@@ -25,6 +25,7 @@
|
||||
<orderEntry type="library" scope="PROVIDED" name="java-i18n" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="gradle-and-groovy-plugin" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="junit-plugin" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="diagram-support" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="intellij-core" level="project" />
|
||||
<orderEntry type="library" name="idea-full" level="project" />
|
||||
</component>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<multiHostInjector implementation="org.jetbrains.kotlin.idea.spring.el.KotlinSpringELInjector"/>
|
||||
|
||||
<codeInsight.lineMarkerProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.spring.lineMarking.KotlinSpringClassAnnotator"/>
|
||||
<codeInsight.lineMarkerProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.spring.lineMarking.KotlinSpringModelDependenciesLineMarkerProvider"/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.spring.inspections.KotlinFinalClassOrFunSpringInspection"
|
||||
displayName="Final Kotlin class or function with Spring annotation"
|
||||
|
||||
@@ -5,4 +5,5 @@
|
||||
<depends optional="true">com.intellij.database</depends>
|
||||
<depends optional="true">com.intellij.css</depends>
|
||||
<depends optional="true">com.intellij.jsp</depends>
|
||||
<depends optional="true">com.intellij.diagram</depends>
|
||||
</idea-plugin>
|
||||
+156
@@ -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<SpringLocalModelDiagramNode>()
|
||||
private val extraEdges = LinkedHashSet<SpringLocalModelDependencyEdge>()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private val nodesSequence: Sequence<SpringLocalModelDiagramNode>
|
||||
get() = (super.getNodes() as Collection<SpringLocalModelDiagramNode>).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<Any>): 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<LocalModel<*>>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
+36
@@ -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)
|
||||
}
|
||||
}
|
||||
+25
@@ -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()
|
||||
}
|
||||
+83
@@ -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<KtClass> { nameIdentifier }?.toLightClass() ?: return false
|
||||
return SpringConfiguration.PSI_CLASS_PATTERN.accepts(lightClass) && SpringCommonUtils.isConfigurationOrMeta(lightClass)
|
||||
}
|
||||
|
||||
override fun doAnnotate(element: PsiElement): LineMarkerInfo<*>? {
|
||||
val klass = element.getParentOfTypeAndBranch<KtClass> { 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<PsiElement>? {
|
||||
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<PsiElement> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user