Skip to content

Commit 26dc021

Browse files
authored
Merge pull request #451 from domaframework/feature/sql-annotation-sqlfile-generation-action-per-class
Enable SQL Annotation Conversion Action from DAO Methods and Control SQL File Opening
2 parents eaf8778 + f4eb99f commit 26dc021

File tree

13 files changed

+263
-112
lines changed

13 files changed

+263
-112
lines changed

src/main/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlAnnotationToFileAction.kt

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ import com.intellij.psi.PsiFile
2626
import com.intellij.psi.PsiMethod
2727
import com.intellij.psi.util.PsiTreeUtil
2828
import org.domaframework.doma.intellij.bundle.MessageBundle
29+
import org.domaframework.doma.intellij.common.dao.getDaoClass
2930
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
3031
import org.domaframework.doma.intellij.common.util.PluginLoggerUtil
31-
import org.domaframework.doma.intellij.extension.psi.DomaAnnotationType
3232

3333
/**
3434
* Intention action to convert @Sql annotation to SQL file
@@ -47,25 +47,15 @@ class ConvertSqlAnnotationToFileAction : PsiElementBaseIntentionAction() {
4747
val psiDaoMethod = PsiDaoMethod(project, method)
4848

4949
// Check if method has @Sql annotation
50-
if (!psiDaoMethod.useSqlAnnotation()) {
50+
// When a Sql annotation is present, a virtual SQL file is associated;
51+
// therefore, check the parent and exclude the injected (inline) SQL.
52+
if (getDaoClass(method.containingFile) == null || !psiDaoMethod.useSqlAnnotation() ||
53+
psiDaoMethod.sqlFile != null && psiDaoMethod.sqlFile?.parent != null
54+
) {
5155
return false
5256
}
5357

54-
// Check if method has @Insert, @Update, or @Delete annotation
55-
val supportedTypes =
56-
listOf(
57-
DomaAnnotationType.Select,
58-
DomaAnnotationType.Script,
59-
DomaAnnotationType.SqlProcessor,
60-
DomaAnnotationType.Insert,
61-
DomaAnnotationType.Update,
62-
DomaAnnotationType.Delete,
63-
DomaAnnotationType.BatchInsert,
64-
DomaAnnotationType.BatchUpdate,
65-
DomaAnnotationType.BatchDelete,
66-
)
67-
68-
return supportedTypes.any { it.getPsiAnnotation(method) != null }
58+
return SqlAnnotationConverter.supportedTypes.any { it.getPsiAnnotation(method) != null }
6959
}
7060

7161
override fun generatePreview(

src/main/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlFileToAnnotationAction.kt

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ import com.intellij.openapi.command.WriteCommandAction
2121
import com.intellij.openapi.editor.Editor
2222
import com.intellij.openapi.project.Project
2323
import com.intellij.psi.PsiElement
24+
import com.intellij.psi.PsiMethod
25+
import com.intellij.psi.util.PsiTreeUtil
2426
import org.domaframework.doma.intellij.bundle.MessageBundle
2527
import org.domaframework.doma.intellij.common.dao.findDaoMethod
28+
import org.domaframework.doma.intellij.common.dao.getDaoClass
29+
import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType
2630
import org.domaframework.doma.intellij.common.isSupportFileType
2731
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
2832
import org.domaframework.doma.intellij.common.util.PluginLoggerUtil
29-
import org.domaframework.doma.intellij.extension.psi.DomaAnnotationType
3033

3134
/**
3235
* Intention action to convert SQL file to @Sql annotation
@@ -41,32 +44,47 @@ class ConvertSqlFileToAnnotationAction : PsiElementBaseIntentionAction() {
4144
editor: Editor?,
4245
element: PsiElement,
4346
): Boolean {
44-
if (!isSupportFileType(element.containingFile)) return false
47+
val file = element.containingFile ?: return false
48+
if (isJavaOrKotlinFileType(file) && getDaoClass(file) != null) {
49+
return checkOnMethod(element, project)
50+
}
51+
52+
if (isSupportFileType(file)) {
53+
return checkOnSqlFile(element, project)
54+
}
4555

56+
return false
57+
}
58+
59+
private fun checkOnMethod(
60+
element: PsiElement,
61+
project: Project,
62+
): Boolean {
63+
val daoMethod = PsiTreeUtil.getParentOfType(element, PsiMethod::class.java) ?: return false
64+
return checkAvailable(project, daoMethod)
65+
}
66+
67+
private fun checkOnSqlFile(
68+
element: PsiElement,
69+
project: Project,
70+
): Boolean {
4671
val daoMethod = findDaoMethod(element.containingFile) ?: return false
72+
return checkAvailable(project, daoMethod)
73+
}
74+
75+
private fun checkAvailable(
76+
project: Project,
77+
daoMethod: PsiMethod,
78+
): Boolean {
4779
val psiDaoMethod = PsiDaoMethod(project, daoMethod)
4880

4981
// Check if method doesn't have @Sql annotation
50-
if (psiDaoMethod.useSqlAnnotation()) {
82+
if (psiDaoMethod.sqlFile == null || psiDaoMethod.useSqlAnnotation()) {
5183
return false
5284
}
5385

54-
// Check if method has @Insert, @Update, or @Delete annotation with sqlFile=true
55-
val supportedTypes =
56-
listOf(
57-
DomaAnnotationType.Select,
58-
DomaAnnotationType.Script,
59-
DomaAnnotationType.SqlProcessor,
60-
DomaAnnotationType.Insert,
61-
DomaAnnotationType.Update,
62-
DomaAnnotationType.Delete,
63-
DomaAnnotationType.BatchInsert,
64-
DomaAnnotationType.BatchUpdate,
65-
DomaAnnotationType.BatchDelete,
66-
)
67-
6886
val hasAnnotation =
69-
supportedTypes.any { type ->
87+
SqlAnnotationConverter.supportedTypes.any { type ->
7088
val annotation = type.getPsiAnnotation(daoMethod)
7189
annotation != null
7290
}
@@ -82,8 +100,42 @@ class ConvertSqlFileToAnnotationAction : PsiElementBaseIntentionAction() {
82100
) {
83101
// Do nothing when previewing
84102
if (IntentionPreviewUtils.isIntentionPreviewActive()) return
85-
if (!isSupportFileType(element.containingFile)) return
86103

104+
val file = element.containingFile
105+
if (isJavaOrKotlinFileType(file)) {
106+
return processOnMethod(element, project)
107+
}
108+
109+
// Process if the file type is SQL
110+
if (isSupportFileType(file)) {
111+
return processOnSqlFile(element, project)
112+
}
113+
}
114+
115+
private fun processOnMethod(
116+
element: PsiElement,
117+
project: Project,
118+
) {
119+
val daoMethod = PsiTreeUtil.getParentOfType(element, PsiMethod::class.java) ?: return
120+
121+
val startTime = System.nanoTime()
122+
val converter = SqlAnnotationConverter(project, daoMethod)
123+
WriteCommandAction.runWriteCommandAction(project) {
124+
converter.convertToSqlAnnotation()
125+
}
126+
127+
PluginLoggerUtil.countLogging(
128+
className = this::class.java.simpleName,
129+
actionName = "convertSqlFileToAnnotationOnMethod",
130+
inputName = "IntentionAction",
131+
start = startTime,
132+
)
133+
}
134+
135+
private fun processOnSqlFile(
136+
element: PsiElement,
137+
project: Project,
138+
) {
87139
val daoMethod = findDaoMethod(element.containingFile) ?: return
88140

89141
val startTime = System.nanoTime()
@@ -94,7 +146,7 @@ class ConvertSqlFileToAnnotationAction : PsiElementBaseIntentionAction() {
94146

95147
PluginLoggerUtil.countLogging(
96148
className = this::class.java.simpleName,
97-
actionName = "convertSqlFileToAnnotation",
149+
actionName = "convertSqlFileToAnnotationOnSQL",
98150
inputName = "IntentionAction",
99151
start = startTime,
100152
)

src/main/kotlin/org/domaframework/doma/intellij/action/dao/SqlAnnotationConverter.kt

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ class SqlAnnotationConverter(
4848
private val psiDaoMethod = PsiDaoMethod(project, method)
4949
private val elementFactory = JavaPsiFacade.getElementFactory(project)
5050

51+
companion object {
52+
val supportedTypes =
53+
setOf(
54+
DomaAnnotationType.Select,
55+
DomaAnnotationType.Script,
56+
DomaAnnotationType.SqlProcessor,
57+
DomaAnnotationType.Insert,
58+
DomaAnnotationType.Update,
59+
DomaAnnotationType.Delete,
60+
DomaAnnotationType.BatchInsert,
61+
DomaAnnotationType.BatchUpdate,
62+
DomaAnnotationType.BatchDelete,
63+
)
64+
}
65+
5166
/**
5267
* Convert @Sql annotation to SQL file
5368
*/
@@ -95,19 +110,6 @@ class SqlAnnotationConverter(
95110
}
96111

97112
private fun findTargetAnnotation(): PsiAnnotation? {
98-
val supportedTypes =
99-
listOf(
100-
DomaAnnotationType.Select,
101-
DomaAnnotationType.Script,
102-
DomaAnnotationType.SqlProcessor,
103-
DomaAnnotationType.Insert,
104-
DomaAnnotationType.Update,
105-
DomaAnnotationType.Delete,
106-
DomaAnnotationType.BatchInsert,
107-
DomaAnnotationType.BatchUpdate,
108-
DomaAnnotationType.BatchDelete,
109-
)
110-
111113
for (type in supportedTypes) {
112114
val annotation = type.getPsiAnnotation(method)
113115
if (annotation != null) {
@@ -243,7 +245,7 @@ class SqlAnnotationConverter(
243245

244246
private fun generateSqlFileWithContent(content: String) {
245247
// First generate the empty SQL file using existing functionality
246-
psiDaoMethod.generateSqlFile()
248+
psiDaoMethod.generateSqlFile(false)
247249

248250
// Then update its content
249251
ApplicationManager.getApplication().invokeLater {

src/main/kotlin/org/domaframework/doma/intellij/common/dao/DaoClass.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ package org.domaframework.doma.intellij.common.dao
1818
import com.intellij.psi.PsiClass
1919
import com.intellij.psi.PsiFile
2020
import com.intellij.psi.util.PsiTreeUtil
21+
import org.domaframework.doma.intellij.common.util.DomaClassName
2122

2223
fun getDaoClass(file: PsiFile): PsiClass? =
2324
PsiTreeUtil
2425
.findChildrenOfType(file, PsiClass::class.java)
25-
.firstOrNull { it.hasAnnotation("org.seasar.doma.Dao") }
26+
.firstOrNull { it.hasAnnotation(DomaClassName.DAO.className) }

src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiDaoMethod.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ class PsiDaoMethod(
169169
}
170170
}
171171

172-
fun generateSqlFile() {
172+
fun generateSqlFile(fileJump: Boolean = true) {
173173
ApplicationManager.getApplication().runReadAction {
174174
if (sqlFilePath.isEmpty()) return@runReadAction
175175
val rootDir = psiProject.getContentRoot(daoFile) ?: return@runReadAction
@@ -202,9 +202,11 @@ class PsiDaoMethod(
202202
.findDirectory(virtualFile) ?: return@runWriteCommandAction
203203
sqlOutputDirPath.findFile(sqlFileName)?.delete()
204204
val sqlVirtualFile = sqlOutputDirPath.createFile(sqlFileName).virtualFile ?: return@runWriteCommandAction
205-
FileEditorManager
206-
.getInstance(psiProject)
207-
.openFile(sqlVirtualFile, true)
205+
if (fileJump) {
206+
FileEditorManager
207+
.getInstance(psiProject)
208+
.openFile(sqlVirtualFile, true)
209+
}
208210
writeEmptyElementSqlFile(sqlVirtualFile)
209211
}
210212
}

src/main/kotlin/org/domaframework/doma/intellij/common/util/DomaClassName.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package org.domaframework.doma.intellij.common.util
1818
enum class DomaClassName(
1919
val className: String,
2020
) {
21+
DAO("org.seasar.doma.Dao"),
22+
2123
OPTIONAL("java.util.Optional"),
2224
OPTIONAL_INT("java.util.OptionalInt"),
2325
OPTIONAL_DOUBLE("java.util.OptionalDouble"),

src/main/resources/META-INF/plugin.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@
106106
<category>Doma tools</category>
107107
<skipBeforeAfter>true</skipBeforeAfter>
108108
</intentionAction>
109+
<intentionAction>
110+
<language>JAVA</language>
111+
<className>org.domaframework.doma.intellij.action.dao.ConvertSqlFileToAnnotationAction</className>
112+
<category>Doma tools</category>
113+
<skipBeforeAfter>true</skipBeforeAfter>
114+
</intentionAction>
109115
</extensions>
110116

111117
<extensions defaultExtensionNs="org.intellij.intelliLang">
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright Doma Tools Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.domaframework.doma.intellij.action.dao
17+
18+
import com.intellij.openapi.fileEditor.FileDocumentManager
19+
import com.intellij.openapi.fileEditor.FileEditorManager
20+
import com.intellij.psi.PsiDocumentManager
21+
import org.domaframework.doma.intellij.DomaSqlTest
22+
23+
abstract class ConvertSqlActionTest : DomaSqlTest() {
24+
protected fun doConvertAction(
25+
daoName: String,
26+
convertFamilyName: String,
27+
sqlConversionPackage: String,
28+
convertActionName: String,
29+
) {
30+
addDaoJavaFile("$sqlConversionPackage/$daoName.java")
31+
32+
val daoClass = findDaoClass("$sqlConversionPackage.$daoName")
33+
myFixture.configureFromExistingVirtualFile(daoClass.containingFile.virtualFile)
34+
println("convertActionName: $convertActionName")
35+
val intention = myFixture.findSingleIntention(convertActionName)
36+
37+
assertNotNull(
38+
"$convertActionName intention should be available",
39+
intention,
40+
)
41+
assertEquals(convertActionName, intention.text)
42+
assertEquals(convertFamilyName, intention.familyName)
43+
44+
myFixture.launchAction(intention)
45+
myFixture.checkResultByFile("java/doma/example/dao/$sqlConversionPackage/$daoName.after.java")
46+
}
47+
48+
protected fun doConvertActionTest(
49+
daoName: String,
50+
sqlToAnnotationPackage: String,
51+
convertFamilyName: String,
52+
) {
53+
addDaoJavaFile("$sqlToAnnotationPackage/$daoName.java")
54+
val daoClass = findDaoClass("$sqlToAnnotationPackage.$daoName")
55+
myFixture.configureFromExistingVirtualFile(daoClass.containingFile.virtualFile)
56+
57+
val intentions = myFixture.availableIntentions
58+
val convertIntention = intentions.find { it is ConvertSqlFileToAnnotationAction }
59+
60+
assertNull("$convertFamilyName intention should NOT be available without @Sql annotation", convertIntention)
61+
}
62+
63+
protected fun doTestSqlFormat(
64+
daoName: String,
65+
sqlFileName: String,
66+
sqlConversionPackage: String,
67+
isScript: Boolean = false,
68+
) {
69+
val openedEditor = FileEditorManager.getInstance(project).selectedEditors
70+
val extension = if (isScript) "script" else "sql"
71+
val openSqlFile = openedEditor.find { it.file.name == sqlFileName.substringAfter("/").plus(".$extension") }
72+
73+
if (openSqlFile != null) {
74+
fail("SQL file $sqlFileName.$extension should be opened after conversion")
75+
return
76+
}
77+
// If the generated `PsiFile` has an associated `Document`, explicitly reload it to ensure memory–disk consistency.
78+
// If not reloaded, the test may produce: *Unexpected memory–disk conflict in tests for*.
79+
val fdm = FileDocumentManager.getInstance()
80+
fdm.saveAllDocuments()
81+
PsiDocumentManager.getInstance(project).commitAllDocuments()
82+
83+
val newSqlFile = findSqlFile("$sqlConversionPackage/$daoName/$sqlFileName.$extension")
84+
if (newSqlFile == null) {
85+
fail("Not Found $sqlFileName.$extension")
86+
return
87+
}
88+
fdm.getDocument(newSqlFile)?.let { fdm.reloadFromDisk(it) }
89+
myFixture.configureFromExistingVirtualFile(newSqlFile)
90+
myFixture.checkResultByFile("resources/META-INF/doma/example/dao/$sqlConversionPackage/$daoName/$sqlFileName.after.$extension")
91+
}
92+
}

0 commit comments

Comments
 (0)