Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

A plugin that adds first-class support for Project Jimmer

![](https://s2.loli.net/2025/04/05/2RNcPghd1AeB8Fs.png)

## Features

### First-class Java or Kotlin development
Expand Down Expand Up @@ -35,32 +33,6 @@ A plugin that adds first-class support for Project Jimmer
- Structure view.
- Visualization to create DTO file.

## Screenshots

![automatically generate class](https://s2.loli.net/2025/03/05/WAxQ34sUnS9i7q5.gif)

![immutables](https://s2.loli.net/2025/03/21/dcZQLJeAhqNSTvH.gif)

![project wizard](https://s2.loli.net/2025/03/05/USP5VdZvlA6iNzO.png)

![generate entity](https://s2.loli.net/2025/03/26/uLvkng5bNHhqeaw.png)

![new dto file](https://s2.loli.net/2025/03/11/gjAWhY8DiOKT5vz.gif)

![inspection](https://s2.loli.net/2025/03/19/GYUof7MaizypW9B.png)

![inspection](https://s2.loli.net/2025/03/19/WSbH2kPVGIwZ4Lr.png)

![nav](https://s2.loli.net/2025/03/20/Kp6ErJH1aNvk8Sl.png)

![generate all set](https://s2.loli.net/2025/03/26/oK5duRqIs2Hb8mj.gif)

![id view completion](https://s2.loli.net/2025/04/03/PlrFSvd42CTw8XZ.gif)

![formula completion](https://s2.loli.net/2025/04/03/j2tM4JePk1hfSBT.gif)

![mapped by](https://s2.loli.net/2025/04/03/fpkjVF7tnSwIKlW.gif)

## FAQ

### Why is the plugin not available
Expand Down
27 changes: 17 additions & 10 deletions core/src/main/kotlin/cn/enaium/jimmer/buddy/dialog/AddDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cn.enaium.jimmer.buddy.dialog

import cn.enaium.jimmer.buddy.storage.JimmerBuddySetting
import cn.enaium.jimmer.buddy.storage.JimmerBuddySetting.DatabaseItem
import cn.enaium.jimmer.buddy.utility.jarFileChooserField
import cn.enaium.jimmer.buddy.utility.fileChooserField
import com.intellij.openapi.components.BaseState
import com.intellij.openapi.observable.properties.PropertyGraph
import com.intellij.openapi.ui.DialogWrapper
Expand All @@ -42,7 +42,7 @@ class AddDatabase(val select: DatabaseItem? = null) : DialogWrapper(false) {
override fun createCenterPanel(): JComponent {
return panel {
row("URI:") {
textField().align(Align.FILL).bindText(databaseModel.uriProperty)
fileChooserField(databaseModel.uriProperty, "sql", true).align(Align.FILL)
}
row("Username:") {
textField().align(Align.FILL).bindText(databaseModel.usernameProperty)
Expand All @@ -59,8 +59,13 @@ class AddDatabase(val select: DatabaseItem? = null) : DialogWrapper(false) {
row("Table Name Pattern:") {
textField().align(Align.FILL).bindText(databaseModel.tableNamePatternProperty)
}
row("Driver File:") {
jarFileChooserField(databaseModel.driverFileProperty).align(Align.FILL)
collapsibleGroup("Driver") {
row("Driver File:") {
fileChooserField(databaseModel.driverFileProperty, "jar").align(Align.FILL)
}
row("Driver Name:") {
textField().align(Align.FILL).bindText(databaseModel.driverNameProperty)
}
}
}
}
Expand All @@ -86,12 +91,13 @@ class AddDatabase(val select: DatabaseItem? = null) : DialogWrapper(false) {
private inner class DatabaseModel : BaseState() {
private val graph: PropertyGraph = PropertyGraph()
val uriProperty = graph.property(select?.uri ?: "")
val usernameProperty = graph.property<String>(select?.username ?: "")
val passwordProperty = graph.property<String>(select?.password ?: "")
val catalogProperty = graph.property<String>(select?.catalog ?: "")
val schemaPatternProperty = graph.property<String>(select?.schemaPattern ?: "")
val tableNamePatternProperty = graph.property<String>(select?.tableNamePattern ?: "")
val driverFileProperty = graph.property<String>(select?.driverFile ?: "")
val usernameProperty = graph.property(select?.username ?: "")
val passwordProperty = graph.property(select?.password ?: "")
val catalogProperty = graph.property(select?.catalog ?: "")
val schemaPatternProperty = graph.property(select?.schemaPattern ?: "")
val tableNamePatternProperty = graph.property(select?.tableNamePattern ?: "")
val driverFileProperty = graph.property(select?.driverFile ?: "")
val driverNameProperty = graph.property(select?.driverName ?: "")

val uri: String by uriProperty
val username: String by usernameProperty
Expand All @@ -100,5 +106,6 @@ class AddDatabase(val select: DatabaseItem? = null) : DialogWrapper(false) {
val schemaPattern: String by schemaPatternProperty
val tableNamePattern: String by tableNamePatternProperty
val driverFile: String by driverFileProperty
val driverName: String by driverNameProperty
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,39 +125,45 @@ class GenerateEntityDialog(
private fun getTables(): Set<Table> {
val uri = databaseItem.uri
val isDDL = uri.startsWith("file:")

var driverJarFile = Path(databaseItem.driverFile).takeIf { it.exists() }
val driverName = databaseItem.driverName.takeIf { it.isNotBlank() }

val jdbcDriver = JdbcDriver.entries.find { uri.startsWith("jdbc:${it.scheme}") } ?: let {
if (isDDL) {
return@let JdbcDriver.H2
} else {
} else if (driverJarFile == null) {
Messages.showErrorDialog(
"Unsupported JDBC Driver(${JdbcDriver.entries.joinToString(", ") { it.scheme }})",
"Error"
)
return emptySet()
} else {
null
}
}

var driverJarFile: Path? = null

listOfNotNull(
Path(System.getProperty("user.home")).resolve(".m2"),
System.getenv("M2_HOME")?.takeIf { it.isNotBlank() }?.let { Path.of(it) },
).forEach { path ->
path.resolve(
"repository/${
jdbcDriver.group.split(".").joinToString("/")
}/${jdbcDriver.artifact}"
).walk().findLast {
it.isDirectory().not()
&& it.name.endsWith("-sources.jar").not()
&& it.name.endsWith("-javadoc.jar").not()
&& it.name.endsWith(".jar")
}?.also {
driverJarFile = it
if (driverJarFile == null && jdbcDriver != null) {
listOfNotNull(
Path(System.getProperty("user.home")).resolve(".m2"),
System.getenv("M2_HOME")?.takeIf { it.isNotBlank() }?.let { Path.of(it) },
).forEach { path ->
path.resolve(
"repository/${
jdbcDriver.group.split(".").joinToString("/")
}/${jdbcDriver.artifact}"
).walk().findLast {
it.isDirectory().not()
&& it.name.endsWith("-sources.jar").not()
&& it.name.endsWith("-javadoc.jar").not()
&& it.name.endsWith(".jar")
}?.also {
driverJarFile = it
}
}
}

if (driverJarFile == null) {
if (driverJarFile == null && jdbcDriver != null) {
listOfNotNull(
Path(System.getProperty("user.home")).resolve(".gradle"),
System.getenv("GRADLE_USER_HOME")?.takeIf { it.isNotBlank() }?.let { Path.of(it) },
Expand All @@ -174,10 +180,6 @@ class GenerateEntityDialog(
}
}

if (driverJarFile == null) {
driverJarFile = Path(databaseItem.driverFile).takeIf { it.exists() }
}

if (driverJarFile == null) {
if (isDDL) {
Messages.showErrorDialog(
Expand All @@ -195,7 +197,7 @@ class GenerateEntityDialog(
DriverManager.registerDriver(
DiverWrapper(
Class.forName(
jdbcDriver.className, true,
jdbcDriver?.className ?: driverName, true,
URLClassLoader(arrayOf(driverJarFile.toUri().toURL()), this.javaClass.classLoader)
).getConstructor()
.newInstance() as Driver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,14 @@
package cn.enaium.jimmer.buddy.extensions.completion

import cn.enaium.jimmer.buddy.utility.annotName
import cn.enaium.jimmer.buddy.utility.isImmutable
import com.intellij.codeInsight.completion.CompletionConfidence
import com.intellij.codeInsight.completion.SkipAutopopupInStrings
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.util.ThreeState
import org.babyfish.jimmer.Immutable
import org.babyfish.jimmer.client.FetchBy
import org.babyfish.jimmer.sql.Entity
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType

/**
* @author Enaium
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2025 Enaium
*
* 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 cn.enaium.jimmer.buddy.extensions.inspection

import cn.enaium.jimmer.buddy.utility.annotations
import cn.enaium.jimmer.buddy.utility.getAllProperties
import cn.enaium.jimmer.buddy.utility.isImmutable
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import org.babyfish.jimmer.sql.Entity
import org.babyfish.jimmer.sql.Id
import org.jetbrains.kotlin.psi.KtClass

/**
* @author Enaium
*/
class IdAnnotationInspection : AbstractLocalInspectionTool() {
override fun visit(
element: PsiElement,
holder: ProblemsHolder,
isOnTheFly: Boolean
) {
val descriptionTemplate = "Entity does not have an id property"
if (element is PsiClass && element.isImmutable() && element.annotations
.any { it.qualifiedName == Entity::class.qualifiedName }
) {
if (!element.allMethods.any { method -> method.annotations.any { it.qualifiedName == Id::class.qualifiedName } }) {
holder.registerProblem(element.nameIdentifier ?: return, descriptionTemplate)
}
} else if (element is KtClass && element.isImmutable() && element.annotations()
.any { it.fqName == Entity::class.qualifiedName }
) {
if (!element.getAllProperties()
.any { property -> property.annotations().any { it.fqName == Id::class.qualifiedName } }
) {
holder.registerProblem(element.nameIdentifier ?: return, descriptionTemplate)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ class ImmutableAnnotationInspection : AbstractLocalInspectionTool() {
holder: ProblemsHolder,
isOnTheFly: Boolean
) {
val problem = if (element is PsiClass && element.hasImmutableAnnotation() && !element.isInterface) {
true
val descriptionTemplate =
"You can not use @Immutable annotation on here because Immutable must be an interface."
if (element is PsiClass && element.hasImmutableAnnotation() && !element.isInterface) {
holder.registerProblem(
element.nameIdentifier ?: return,
descriptionTemplate
)
} else if (element is KtClass && element.hasImmutableAnnotation() && !element.isInterface()) {
true
} else {
false
}
if (problem) {
holder.registerProblem(
element,
"You can not use @Immutable annotation on here because Immutable must be an interface."
element.nameIdentifier ?: return,
descriptionTemplate
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2025 Enaium
*
* 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 cn.enaium.jimmer.buddy.extensions.inspection

import cn.enaium.jimmer.buddy.utility.annotations
import cn.enaium.jimmer.buddy.utility.type
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiJavaCodeReferenceElement
import com.intellij.psi.PsiReferenceList
import org.babyfish.jimmer.sql.MappedSuperclass
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType

/**
* @author Enaium
*/
class SuperTypeInspection : AbstractLocalInspectionTool() {
override fun visit(
element: PsiElement,
holder: ProblemsHolder,
isOnTheFly: Boolean
) {
val descriptionTemplate = "Super type must be annotated with @MappedSuperclass"
if (element is PsiClass) {
element.getChildrenOfType<PsiReferenceList>().forEach { psiReferenceList ->
psiReferenceList.getChildrenOfType<PsiJavaCodeReferenceElement>()
.forEach { psiJavaCodeReferenceElement ->
if ((psiJavaCodeReferenceElement.resolve() as? PsiClass)?.annotations?.any { annotation -> annotation.qualifiedName == MappedSuperclass::class.qualifiedName } == false) {
holder.registerProblem(psiJavaCodeReferenceElement, descriptionTemplate)
}
}
}
} else if (element is KtClass) {
element.superTypeListEntries.forEach { superTypeListEntry ->
superTypeListEntry.typeReference?.type()?.ktClass?.annotations()
?.find { annotation -> annotation.fqName == MappedSuperclass::class.qualifiedName }
?: run {
holder.registerProblem(superTypeListEntry, descriptionTemplate)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class JimmerBuddySetting : PersistentStateComponent<JimmerBuddySetting.Setting>
var schemaPattern: String = "",
var tableNamePattern: String = "",
var driverFile: String = "",
var driverName: String = "",
)

data class JavaToKotlin(
Expand Down
Loading