Spring Support: Model diagram support for Kotlin classes
#KT-11692 In Progress
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
New features:
|
New features:
|
||||||
|
|
||||||
- [KT-11768](https://youtrack.jetbrains.com/issue/KT-11768) Implement "Introduce local variable" intention
|
- [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:
|
Issues fixed:
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ class KtLightAnnotation(
|
|||||||
|
|
||||||
override fun getParent() = owner as? PsiElement
|
override fun getParent() = owner as? PsiElement
|
||||||
|
|
||||||
|
override fun delete() {
|
||||||
|
kotlinOrigin.delete()
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString() = "@$qualifiedName"
|
override fun toString() = "@$qualifiedName"
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
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="java-i18n" level="project" />
|
||||||
<orderEntry type="library" scope="PROVIDED" name="gradle-and-groovy-plugin" 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="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" scope="PROVIDED" name="intellij-core" level="project" />
|
||||||
<orderEntry type="library" name="idea-full" level="project" />
|
<orderEntry type="library" name="idea-full" level="project" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<multiHostInjector implementation="org.jetbrains.kotlin.idea.spring.el.KotlinSpringELInjector"/>
|
<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.KotlinSpringClassAnnotator"/>
|
||||||
|
<codeInsight.lineMarkerProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.spring.lineMarking.KotlinSpringModelDependenciesLineMarkerProvider"/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.spring.inspections.KotlinFinalClassOrFunSpringInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.spring.inspections.KotlinFinalClassOrFunSpringInspection"
|
||||||
displayName="Final Kotlin class or function with Spring annotation"
|
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.database</depends>
|
||||||
<depends optional="true">com.intellij.css</depends>
|
<depends optional="true">com.intellij.css</depends>
|
||||||
<depends optional="true">com.intellij.jsp</depends>
|
<depends optional="true">com.intellij.jsp</depends>
|
||||||
|
<depends optional="true">com.intellij.diagram</depends>
|
||||||
</idea-plugin>
|
</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