feat: Initial commit of the ActionBean generator plugin

This commit includes the basic project structure, the initial implementation of the plugin, and fixes for duplicate API calls and deprecated code.
This commit is contained in:
2025-07-08 19:13:39 +07:00
commit fb3b704285
39 changed files with 1509 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
package com.sdk.actionbean.generator;
import com.intellij.database.model.*;
import com.intellij.database.psi.*;
import com.intellij.database.util.*;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.*;
import com.intellij.openapi.fileChooser.*;
import com.intellij.openapi.project.*;
import com.intellij.openapi.ui.*;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.*;
import com.intellij.util.containers.*;
import org.jetbrains.annotations.*;
import java.io.*;
import java.nio.charset.*;
import java.util.*;
import java.util.HashMap;
public class GenerateAction extends AnAction {
private static final LinkedHashMap<String, String> TYPE_MAPPING = new LinkedHashMap<>();
static {
TYPE_MAPPING.put("bigint", "Long");
TYPE_MAPPING.put("bit", "Boolean");
TYPE_MAPPING.put("boolean", "Boolean");
TYPE_MAPPING.put("date", "java.util.Date");
TYPE_MAPPING.put("decimal", "java.math.BigDecimal");
TYPE_MAPPING.put("double", "Double");
TYPE_MAPPING.put("float", "Float");
TYPE_MAPPING.put("integer", "Integer");
TYPE_MAPPING.put("numeric", "java.math.BigDecimal");
TYPE_MAPPING.put("smallint", "Short");
TYPE_MAPPING.put("timestamp", "java.util.Date");
TYPE_MAPPING.put("text", "String");
TYPE_MAPPING.put("time", "java.util.Date");
TYPE_MAPPING.put("tinyint", "Byte");
TYPE_MAPPING.put("varchar2", "String");
TYPE_MAPPING.put("varchar", "String");
TYPE_MAPPING.put("char", "String");
}
private static final Map<String,String> userDefType = new HashMap<>();
private String getRawType(String columnDefinition ) {
for (String rawType : TYPE_MAPPING.keySet()) {
if (columnDefinition.contains(rawType)) {
return rawType;
}
}
return "varchar";
}
private void createUserDefineType(DbTable table) {
if (userDefType.isEmpty()) {
String schemaName = table.getParent() != null ? table.getParent().getName() : "";
DasUtil.getCatalogObject(table).getDasChildren(ObjectKind.SCHEMA).forEach(schema -> {
if (schema.getName().equals("public") || schema.getName().equals(schemaName)) {
schema.getDasChildren(ObjectKind.OBJECT_TYPE).forEach(domain -> {
String domainName = domain.getName();
String domainScript = ((DbObject) domain).getText();
userDefType.put(domainName, getRawType(domainScript));
});
}
});
}
}
private String getFieldType(String columnType) {
columnType = columnType.toLowerCase();
String fieldType = TYPE_MAPPING.get(columnType );
if (fieldType == null) {
fieldType = TYPE_MAPPING.getOrDefault(userDefType.getOrDefault(fieldType,"varchar"),"Object" );
}
return fieldType;
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getProject();
PsiElement[] psiElements = e.getData(LangDataKeys.PSI_ELEMENT_ARRAY);
if (project == null || psiElements == null || psiElements.length == 0) {
return;
}
for (PsiElement psiElement : psiElements) {
if (psiElement instanceof DbTable) {
generate((DbTable) psiElement, project);
}
}
}
private void generate(DbTable table, Project project) {
if (userDefType.isEmpty()) {
createUserDefineType(table);
}
String className = toCamelCase(table.getName(), true);
StringBuilder builder = new StringBuilder();
builder.append("public class ").append(className).append(" {\n\n");
JBIterable<? extends DasColumn> columns = DasUtil.getColumns(table);
for (DasColumn column : columns) {
String fieldName = toCamelCase(column.getName(), false);
String fieldType = getFieldType(column.getDasType().toDataType().typeName);
builder.append(" private ").append(fieldType).append(" ").append(fieldName).append(";\n");
}
builder.append("\n");
for (DasColumn column : columns) {
String fieldName = toCamelCase(column.getName(), false);
String fieldType = getFieldType(column.getDasType().toDataType().typeName);
String capitalizedFieldName = capitalize(fieldName);
builder.append(" public ").append(fieldType).append(" get").append(capitalizedFieldName).append("() {\n");
builder.append(" return ").append(fieldName).append(";\n");
builder.append(" }\n\n");
builder.append(" public void set").append(capitalizedFieldName).append("(").append(fieldType).append(" ").append(fieldName).append(") {\n");
builder.append(" this.").append(fieldName).append(" = ").append(fieldName).append(";\n");
builder.append(" }\n\n");
}
builder.append("}");
FileSaverDescriptor descriptor = new FileSaverDescriptor("Save JavaBean", "Save the generated JavaBean to a file", "java");
FileSaverDialog saveFileDialog = FileChooserFactory.getInstance().createSaveFileDialog(descriptor, project);
VirtualFileWrapper fileWrapper = saveFileDialog.save((VirtualFile) null, className + ".java");
VirtualFile virtualFile = fileWrapper != null ? fileWrapper.getVirtualFile() : null;
if (virtualFile != null) {
ApplicationManager.getApplication().runWriteAction(() -> {
try (OutputStream outputStream = virtualFile.getOutputStream(this)) {
outputStream.write(builder.toString().getBytes(StandardCharsets.UTF_8));
} catch (IOException ex) {
Messages.showErrorDialog(project, "Error saving file: " + ex.getMessage(), "Error");
}
});
}
}
private String toCamelCase(String s, boolean capitalizeFirst) {
String[] parts = s.split("_");
StringBuilder camelCaseString = new StringBuilder();
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
if (i == 0 && !capitalizeFirst) {
camelCaseString.append(part.toLowerCase());
} else {
camelCaseString.append(capitalize(part));
}
}
return camelCaseString.toString();
}
private String capitalize(String s) {
if (s == null || s.isEmpty()) {
return s;
}
return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
}
}

View File

@@ -0,0 +1,87 @@
package com.sdk.actionbean.generator;
import com.intellij.database.psi.DbTable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserFactory;
import com.intellij.openapi.fileChooser.PathChooserDialog;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.roots.PackageIndex;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class GenerateBeanAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getProject();
PsiElement[] psiElements = e.getData(LangDataKeys.PSI_ELEMENT_ARRAY);
if (project == null || psiElements == null || psiElements.length == 0) {
return;
}
FileChooserDescriptor descriptor = new FileChooserDescriptor(false,true,false,false,false,false);
PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null);
VirtualFile baseDir = ProjectUtil.guessProjectDir(project);
pathChooser.choose(baseDir, virtualFiles -> {
String packageName = getSelectedPackage(project, virtualFiles.getFirst());
ArrayList<DbTable> tables = new ArrayList<>();
for (PsiElement psiElement : psiElements) {
if (psiElement instanceof DbTable) {
tables.add((DbTable) psiElement);
}
}
runGenerator(project, tables, packageName);
});
}
private void runGenerator(Project project,ArrayList<DbTable> tables, String packageName) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Generate Database Action Models ...") {
@Override
public void run(@NotNull ProgressIndicator indicator) {
ApplicationManager.getApplication().invokeLater(() ->
WriteCommandAction.runWriteCommandAction(project, () -> {
try {
new GeneratorServices(project,tables,packageName).execute(indicator);
} catch (Exception ex) {
showError(project, "An error occurred during code generation: " + ex.getMessage());
}
})
);
}
});
}
private void showError(Project project, String message) {
ApplicationManager.getApplication().invokeLater(() -> Messages.showErrorDialog(project, message, "Generation Failed"));
}
private String getSelectedPackage(Project project, VirtualFile selectDirectory) {
if (selectDirectory == null) return "";
String packageName = PackageIndex.getInstance(project).getPackageNameByDirectory(selectDirectory);
return packageName;
}
private VirtualFile findSourceRoot(Project project, String packageName) {
for (VirtualFile root : ProjectRootManager.getInstance(project).getContentSourceRoots()) {
if (root.isDirectory() && !root.getName().equals("resources")) {
return root;
}
}
return null;
}
}

View File

@@ -0,0 +1,110 @@
package com.sdk.actionbean.generator;
import com.intellij.database.psi.DbTable;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;
import java.util.stream.Collectors;
public class GeneratorServices {
private final String packageName;
private final Project project;
private final ArrayList<DbTable> tables;
public GeneratorServices(Project project, ArrayList<DbTable> tables, String packageName) {
this.tables = tables;
this.project = project;
this.packageName = packageName;
}
public void execute(@NotNull ProgressIndicator indicator) {
// String packagePath = packageName.replace('.', '/');
// VirtualFile packageDir = createPackageDirs(sourceRoot, packagePath);
//
// Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
// cfg.setClassForTemplateLoading(this.getClass(), "/templates");
// cfg.setDefaultEncoding("UTF-8");
// cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
// Template template = cfg.getTemplate("actionBean.ftl");
//
// DatabaseMetaData metaData = conn.getMetaData();
// try (ResultSet tables = metaData.getTables(null, dbSchema.toUpperCase(), tablePattern, new String[]{"TABLE"})) {
// while (tables.next()) {
// String tableName = tables.getString("TABLE_NAME");
// indicator.setText("Processing table: " + tableName);
//
// Map<String, Object> model = createModelForTable(metaData, tableName, dbSchema);
//
// VirtualFile outputFile = packageDir.findOrCreateChildData(this, model.get("className") + ".java");
// try (Writer writer = new OutputStreamWriter(outputFile.getOutputStream(this))) {
// template.process(model, writer);
// }
// indicator.setText2("Generated " + outputFile.getName());
// }
// }
}
private Map<String, Object> createModelForTable(DatabaseMetaData metaData, String tableName, String dbSchema) throws SQLException {
Map<String, Object> model = new HashMap<>();
model.put("packageName", packageName);
model.put("tableName", tableName);
model.put("className", tableName);
model.put("dbSchema", dbSchema);
List<Map<String, Object>> columns = new ArrayList<>();
Set<String> primaryKeys = new HashSet<>();
try (ResultSet pks = metaData.getPrimaryKeys(null, dbSchema.toUpperCase(), tableName)) {
while (pks.next()) {
primaryKeys.add(pks.getString("COLUMN_NAME"));
}
}
try (ResultSet cols = metaData.getColumns(null, dbSchema.toUpperCase(), tableName, "%")) {
while (cols.next()) {
Map<String, Object> colModel = new HashMap<>();
String colName = cols.getString("COLUMN_NAME");
int dataType = cols.getInt("DATA_TYPE");
colModel.put("name", colName);
colModel.put("isPk", primaryKeys.contains(colName));
colModel.put("customType", mapJdbcType(dataType));
columns.add(colModel);
}
}
model.put("columns", columns);
model.put("fieldList", columns.stream().map(c -> (String) c.get("name")).collect(Collectors.joining(",")));
model.put("keyList", String.join(",", primaryKeys));
return model;
}
private VirtualFile createPackageDirs(VirtualFile sourceRoot, String path) throws java.io.IOException {
VirtualFile current = sourceRoot;
for (String part : path.split("/")) {
VirtualFile child = current.findChild(part);
if (child == null) {
child = current.createChildDirectory(this, part);
}
current = child;
}
return current;
}
private String mapJdbcType(int jdbcType) {
return switch (jdbcType) {
case Types.NUMERIC, Types.DECIMAL, Types.INTEGER, Types.BIGINT, Types.SMALLINT, Types.TINYINT, Types.FLOAT, Types.DOUBLE -> "NUMBER";
case Types.DATE, Types.TIMESTAMP, Types.TIMESTAMP_WITH_TIMEZONE -> "DATE";
default -> "STRING";
};
}
}

View File

@@ -0,0 +1,28 @@
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<idea-plugin>
<!-- Unique id for this plugin. Must stay constant for the life of the plugin. -->
<id>com.sdk.actionbean.generator</id>
<name>Database Action Models Generator</name>
<vendor>Sakda Sakprapakorn</vendor>
<description><![CDATA[
Generates ActionBean Java classes from a database schema.
Right-click on a package and select "Generate Database Action Models" to start.
]]></description>
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.modules.java</depends>
<depends>com.intellij.database</depends>
<actions>
<!-- Defines an action that will appear in the context menu for project directories -->
<action id="ActionModelGenerator.GenerateAction"
class="com.sdk.actionbean.generator.GenerateBeanAction"
text="Generate Database Action Models"
description="Generates ActionBean classes from a database schema.">
<add-to-group group-id="GenerateGroup" anchor="first"/>
<!-- This makes the action visible only when right-clicking a source package -->
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt G"/>
</action>
</actions>
</idea-plugin>

View File

@@ -0,0 +1,11 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M32.0845 7.94025V4H24.0203V7.9896H16.029V4H7.91553V7.94025H4V36H16.0044V32.0045C16.0058 30.9457 16.4274 29.9308 17.1766 29.1826C17.9258 28.4345 18.9412 28.0143 20 28.0143C21.0588 28.0143 22.0743 28.4345 22.8234 29.1826C23.5726 29.9308 23.9942 30.9457 23.9956 32.0045V36H36V7.94025H32.0845Z"
fill="url(#paint0_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="2.94192" y1="4.89955" x2="37.7772" y2="39.7345" gradientUnits="userSpaceOnUse">
<stop offset="0.15937" stop-color="#3BEA62"/>
<stop offset="0.5404" stop-color="#3C99CC"/>
<stop offset="0.93739" stop-color="#6B57FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 794 B

View File

@@ -0,0 +1,18 @@
package ${packageName}.bean;
/**
* Generated by IntelliJ ActionBean Generator
* Table: ${tableName}
*/
import sdk.dbutils.*;
public class ${tableName} extends ${packageName}.bean.base.${tableName} {
public ${tableName}(DBConnector connector) { //class construction
super(connector);
}
public ${tableName}() { //class construction
super();
}
}

View File

@@ -0,0 +1,103 @@
package ${packageName}.bean.base;
/**
* Generated by IntelliJ ActionBean Generator
* Table: ${tableName}
*/
import sdk.dbutils.*;
import sdk.utils.*;
import ${basePackage}.dto.*;
public class ${className} extends ActionBean {
public static final String tableName = "${tableName}";
public static final String schemaName = "${dbSchema}";
public static final String fullTableName = "${tableName}";
private DTO_${tableName} myDTO;
public ${className}(DBConnector dbConnector) { //class construction
super(dbConnector, fullTableName);
myDTO = new DTO_${tableName}();
initFieldDefs();
fieldList = "${fieldList}";
keyList = "${keyList}";
}
public ${className}() { //class construction
super();
myDTO = new DTO_${tableName}();
initFieldDefs();
fieldList = "${fieldList}";
keyList = "${keyList}";
}
@Override
protected void initFieldDefs() {
fieldDefs.clear();
<#list columns as col>
fieldDefs.put("${col.name}", "${col.customType}<#if col.isPk>:KEY</#if>");
</#list>
}
public DTO_${tableName} getInstanceDTO() {
myDTO.setDTO(this.getDTO());
return myDTO;
}
@Override
protected void initSqlSelect() {
StringBuilder Sql = new StringBuilder();
Sql.append("SELECT ");
<#list columns as col>
<#if col?is_first>
Sql.append(" <#if col.customType == 'DATE'>JDTOET(${col.name}) ${col.name}, ${col.name} AS DT_${col.name}<#else> ${col.name}</#if>\n");
<#else>
Sql.append(" ,<#if col.customType == 'DATE'>JDTOET(${col.name}) ${col.name}, ${col.name} AS DT_${col.name}<#else>${col.name}</#if>\n");
</#if>
</#list>
Sql.append(" FROM ${tableName}\n");
Sql.append(" WHERE 0=0 \n");
sqlSelect = new StringBuilder(Sql);
}
<#-- Generate Setters -->
<#list columns as col>
public void set${col.name}(String value) {
<#if col.isPk>
if (JUtils.isMacro(value)) {
setKeyField(${col.name}, value);
fieldByName(${col.name}).setAsString(value);
} else {
setKeyField(${col.name}, JUtils.cleanUpNumber(value));
fieldByName(${col.name}).setAsString(JUtils.cleanUpNumber(value));
}
<#elseif col.customType == "DATE">
if (JUtils.isMacro(value)) {
setField(${col.name}, value);
fieldByName(${col.name}).setAsString(value);
} else {
setField(${col.name}, DateUtils.strToSqlDate(value));
fieldByName(${col.name}).setAsString(value);
}
<#else>
setField(${col.name}, value);
fieldByName(${col.name}).setAsString(value);
</#if>
}
</#list>
<#-- Generate Getters and Constants -->
<#list columns as col>
public static final String ${col.name} = "${col.name}";
public String get${col.name}() {
return getValue(${col.name});
}
public String get${col.name}(String defValue) {
return getValue(${col.name}, defValue);
}
</#list>
}