Spring Support: Model diagram support for Kotlin classes

#KT-11692 In Progress
This commit is contained in:
Alexey Sedunov
2016-04-08 18:45:37 +03:00
parent 38522b09ce
commit 548d0cd4e5
10 changed files with 318 additions and 0 deletions
+1
View File
@@ -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
View File
@@ -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>
+1
View File
@@ -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>
+1
View File
@@ -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>
@@ -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)
}
}
@@ -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)
}
}
@@ -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()
}
@@ -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))
}
}
}