feat: implement Dataset XML generation and Action Models V3

- Added 'Generate Dataset XML' feature to automate table definitions from database schema.
- Implemented intelligent label generation (prioritizing comments) and automatic type mapping for XML.
- Added 'Generate Action Models V3' supporting the new sdk.db package structure.
- Updated release notes and bumped version to 2.1.0.
- Registered new actions with keyboard shortcuts in plugin.xml.
This commit is contained in:
2026-02-27 12:33:58 +07:00
parent c40ce04f73
commit d501ecec22
12 changed files with 313 additions and 50 deletions

0
DevResources/Dataset.xml Normal file
View File

View File

@@ -4,8 +4,8 @@ plugins {
id("org.jetbrains.intellij.platform") version "2.7.0"
}
group = "com.sdk.generators.actionmodels"
version = "1.1.2"
group = "com.sdk.generators.actionmodels.v3"
version = "2.1.0"
repositories {
mavenCentral()
@@ -37,27 +37,37 @@ intellijPlatform {
}
changeNotes = """
## Release Notes
## [1.1.2]
fix: Update plugin version display
Corrected the plugin version display in the IDE.
## [1.1.1]
fix: Correctly generate primary keys
The generator was failing to identify primary key columns, resulting in generated beans without them. This has been fixed by updating the code to use the `getColumnsRef()` method, which correctly retrieves the column names for the primary key.
## [1.1.0]
### New Features and Enhancements
* **DTO Generation:** Introduced new functionality to generate Data Transfer Object (DTO) classes alongside ActionBeans. This includes new FreeMarker templates (`actionDTO.ftl`, `actionDTO.extend.ftl`) and updated logic in `GeneratorServices.java`.
* **ActionField and DTOField Enhancements:** Implemented enhancements related to the generation of `ActionField` and `DTOField` within the generated classes.
### Refactoring and Improvements
* **Project Structure Refactoring:** The project structure has been reorganized. Generator-related classes were moved to a new package (`com.sdk.generators.actionmodels`), and template directory names were standardized to `src/main/resources/templates`.
* **Build System Updates:** Updated `build.gradle.kts`, `plugin.xml`, and Gradle wrapper files to reflect the structural and functional enhancements.
<h2>[2.1.0]</h2>
<ul>
<li>Add: Generate Dataset XML feature to generate XML table definitions from database tables.</li>
<li>Feature: Intelligent label generation for XML datasets, prioritizing database column comments with fallback to entity-based keys.</li>
<li>Feature: Automatic mapping of database types to XML types (TEXT, NUMBER, DATE) and column width extraction.</li>
<li>Shortcut: Added <code>Ctrl + Alt + J</code> for the dataset generation action.</li>
</ul>
<h2>[2.0.1]</h2>
<ul>
<li>update : Refactory class structure of sdk.dbutils -> sdk.db.xxxx</li>
<li>Add : Generate for new class structure as V3 </li>
</ul>
<h2>[1.1.2]</h2>
<ul>
<li>fix: Handle databases that do not support user-defined types</li>
</ul>
<h2>[1.1.1]</h2>
<ul>
<li>fix: Correctly generate primary keys. The generator was failing to identify primary key columns, resulting in generated beans without them. This has been fixed by updating the code to use the `getColumnsRef()` method, which correctly retrieves the column names for the primary key.</li>
</ul>
<h2>[1.1.0]</h2>
<h3>New Features and Enhancements</h3>
<ul>
<li><strong>DTO Generation:</strong> Introduced new functionality to generate Data Transfer Object (DTO) classes alongside ActionBeans. This includes new FreeMarker templates (<code>actionDTO.ftl</code>, <code>actionDTO.extend.ftl</code>) and updated logic in <code>GeneratorServices.java</code>.</li>
<li><strong>ActionField and DTOField Enhancements:</strong> Implemented enhancements related to the generation of <code>ActionField</code> and <code>DTOField</code> within the generated classes.</li>
</ul>
<h3>Refactoring and Improvements</h3>
<ul>
<li><strong>Project Structure Refactoring:</strong> The project structure has been reorganized. Generator-related classes were moved to a new package (<code>com.sdk.generators.actionmodels</code>), and template directory names were standardized to <code>src/main/resources/templates</code>.</li>
<li><strong>Build System Updates:</strong> Updated <code>build.gradle.kts</code>, <code>plugin.xml</code>, and Gradle wrapper files to reflect the structural and functional enhancements.</li>
</ul>
"""
}
}

View File

@@ -48,13 +48,13 @@ public class GenerateBeanAction extends AnAction {
}
private void runGenerator(Project project,ArrayList<DbTable> tables, String packageName) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Generate Database Action Models ...") {
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);
new GeneratorServices(project,tables,packageName, GeneratorServices.Version.V2).execute(indicator);
} catch (Exception ex) {
GUtils.showError(project, "An error occurred during code generation: " + ex.getMessage());
}

View File

@@ -0,0 +1,66 @@
package com.sdk.generators.actionmodels;
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.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.sdk.generators.GUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class GenerateBeanActionV3 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 = GUtils.findSourceRoot(project);
pathChooser.choose(baseDir, virtualFiles -> {
String packageName = GUtils.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,"V3");
});
}
private void runGenerator(Project project,ArrayList<DbTable> tables, String packageName, String version) {
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, GeneratorServices.Version.V3).execute(indicator);
} catch (Exception ex) {
GUtils.showError(project, "An error occurred during code generation: " + ex.getMessage());
}
})
);
}
});
}
}

View File

@@ -0,0 +1,69 @@
package com.sdk.generators.actionmodels;
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.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.sdk.generators.GUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class GenerateDatasetAction 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);
descriptor.setTitle("Select Target Directory for Dataset XML");
PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null);
//VirtualFile baseDir = GUtils.findSourceRoot(project);
VirtualFile baseDir = project.getBaseDir();
pathChooser.choose(baseDir, virtualFiles -> {
VirtualFile targetDir = virtualFiles.getFirst();
ArrayList<DbTable> tables = new ArrayList<>();
for (PsiElement psiElement : psiElements) {
if (psiElement instanceof DbTable) {
tables.add((DbTable) psiElement);
}
}
runGenerator(project, tables, targetDir);
});
}
private void runGenerator(Project project, ArrayList<DbTable> tables, VirtualFile targetDir) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Generate Dataset XML ...") {
@Override
public void run(@NotNull ProgressIndicator indicator) {
ApplicationManager.getApplication().invokeLater(() ->
WriteCommandAction.runWriteCommandAction(project, () -> {
try {
new GeneratorServices(project, tables, "", GeneratorServices.Version.V2).executeDataset(targetDir, indicator);
} catch (Exception ex) {
GUtils.showError(project, "An error occurred during code generation: " + ex.getMessage());
}
})
);
}
});
}
}

View File

@@ -28,11 +28,15 @@ public class GeneratorServices {
private final String basePackage;
private final Project project;
private final ArrayList<DbTable> tables;
private final Version version;
public GeneratorServices(Project project, ArrayList<DbTable> tables, String basePackage) {
public enum Version {V2,V3}
public GeneratorServices(Project project, ArrayList<DbTable> tables, String basePackage, Version version) {
this.tables = tables;
this.project = project;
this.basePackage = basePackage;
this.version = version;
}
private void genDataModel(Template template, Map<String, Object> model, VirtualFile targetDir, String classFile, ProgressIndicator indicator) {
@@ -111,6 +115,33 @@ public class GeneratorServices {
}
}
public void executeDataset(VirtualFile targetDir, @NotNull ProgressIndicator indicator) {
AtomicInteger fileCount = new AtomicInteger(0);
try {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
cfg.setClassForTemplateLoading(this.getClass(), "/templates");
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
Template tmpDataset = cfg.getTemplate("dataset.ftl");
tables.forEach(table -> {
Map<String, Object> model = createModelForTable(table);
String tableName = model.get("tableName").toString();
String fileName = GUtils.capitalize(tableName) + ".xml";
genDataModel(tmpDataset, model, targetDir, fileName, indicator);
fileCount.getAndIncrement();
});
String message = String.format("Generated %d dataset XML files successfully.", fileCount.get());
GUtils.showInfo(project, "Dataset XML Generation Complete", message);
} catch (Exception ex) {
GUtils.showError(project, "Generation Failed \n" + ex.getMessage());
}
}
private Map<String, Object> createModelForTable(DbTable table) {
String tableName = table.getName().toUpperCase();
String dbSchema = table.getParent() != null ? table.getParent().getName() : "";
@@ -123,6 +154,16 @@ public class GeneratorServices {
model.put("className", tableName);
model.put("dbSchema", dbSchema);
if (version == Version.V2) {
model.put("db_connector", "sdk.dbutils.*");
model.put("db_dataset", "sdk.dbutils.*");
model.put("db_dto", "sdk.dbutils.*");
} else {
model.put("db_connector", "sdk.db.connector.*");
model.put("db_dataset", "sdk.db.dataset.*");
model.put("db_dto", "sdk.db.dto.*");
}
List<Map<String, Object>> columns = new ArrayList<>();
Set<String> primaryKeys = new HashSet<>();
JBIterable<? extends DasTableKey> dasKeys = DasUtil.getTableKeys(table);
@@ -133,6 +174,15 @@ public class GeneratorServices {
}
});
String entityName = tableName.toLowerCase();
if (entityName.endsWith("_m")) {
entityName = entityName.substring(0, entityName.length() - 2);
} else if (entityName.endsWith("s")) {
entityName = entityName.substring(0, entityName.length() - 1);
}
final String finalEntityName = entityName;
JBIterable<? extends DasColumn> dasColumns = DasUtil.getColumns(table);
dasColumns.forEach(column -> {
Map<String, Object> colModel = new HashMap<>();
@@ -141,6 +191,28 @@ public class GeneratorServices {
colModel.put("name", colName);
colModel.put("isPk", primaryKeys.contains(colName));
colModel.put("customType", dataType);
// XML Specific fields
String xmlType = "TEXT";
if ("NUMBER".equals(dataType)) {
xmlType = "NUMBER";
} else if ("DATE".equals(dataType)) {
xmlType = "DATE";
}
colModel.put("xmlType", xmlType);
int width = column.getDasType().toDataType().getLength();
colModel.put("width", Math.max(width, 0));
// Label generation: Use field comment if available, otherwise fallback to generated key
String comment = column.getComment();
if (comment != null && !comment.isEmpty()) {
colModel.put("label", comment);
} else {
String fieldPart = colName.toLowerCase();
colModel.put("label", fieldPart);
}
columns.add(colModel);
});

View File

@@ -11,26 +11,37 @@
]]></description>
<change-notes><![CDATA[
## [1.1.2]
fix: Update plugin version display
Corrected the plugin version display in the IDE.
## [1.1.1]
fix: Correctly generate primary keys
The generator was failing to identify primary key columns, resulting in generated beans without them. This has been fixed by updating the code to use the `getColumnsRef()` method, which correctly retrieves the column names for the primary key.
## Release Notes
### New Features and Enhancements
* **DTO Generation:** Introduced new functionality to generate Data Transfer Object (DTO) classes alongside ActionBeans. This includes new FreeMarker templates (`actionDTO.ftl`, `actionDTO.extend.ftl`) and updated logic in `GeneratorServices.java`.
* **ActionField and DTOField Enhancements:** Implemented enhancements related to the generation of `ActionField` and `DTOField` within the generated classes.
### Refactoring and Improvements
* **Project Structure Refactoring:** The project structure has been reorganized. Generator-related classes were moved to a new package (`com.sdk.generators.actionmodels`), and template directory names were standardized to `src/main/resources/templates`.
* **Build System Updates:** Updated `build.gradle.kts`, `plugin.xml`, and Gradle wrapper files to reflect the structural and functional enhancements.
<h2>[2.1.0]</h2>
<ul>
<li>Add: Generate Dataset XML feature to generate XML table definitions from database tables.</li>
<li>Feature: Intelligent label generation for XML datasets, prioritizing database column comments with fallback to entity-based keys.</li>
<li>Feature: Automatic mapping of database types to XML types (TEXT, NUMBER, DATE) and column width extraction.</li>
<li>Shortcut: Added <code>Ctrl + Alt + J</code> for the dataset generation action.</li>
</ul>
<h2>[2.0.1]</h2>
<ul>
<li>update : Refactory class structure of sdk.dbutils -> sdk.db.xxxx</li>
<li>Add : Generate for new class structure as V3 </li>
</ul>
<h2>[1.1.2]</h2>
<ul>
<li>fix: Handle databases that do not support user-defined types</li>
</ul>
<h2>[1.1.1]</h2>
<ul>
<li>fix: Correctly generate primary keys. The generator was failing to identify primary key columns, resulting in generated beans without them. This has been fixed by updating the code to use the `getColumnsRef()` method, which correctly retrieves the column names for the primary key.</li>
</ul>
<h2>[1.1.0]</h2>
<h3>New Features and Enhancements</h3>
<ul>
<li><strong>DTO Generation:</strong> Introduced new functionality to generate Data Transfer Object (DTO) classes alongside ActionBeans. This includes new FreeMarker templates (<code>actionDTO.ftl</code>, <code>actionDTO.extend.ftl</code>) and updated logic in <code>GeneratorServices.java</code>.</li>
<li><strong>ActionField and DTOField Enhancements:</strong> Implemented enhancements related to the generation of <code>ActionField</code> and <code>DTOField</code> within the generated classes.</li>
</ul>
<h3>Refactoring and Improvements</h3>
<ul>
<li><strong>Project Structure Refactoring:</strong> The project structure has been reorganized. Generator-related classes were moved to a new package (<code>com.sdk.generators.actionmodels</code>), and template directory names were standardized to <code>src/main/resources/templates</code>.</li>
<li><strong>Build System Updates:</strong> Updated <code>build.gradle.kts</code>, <code>plugin.xml</code>, and Gradle wrapper files to reflect the structural and functional enhancements.</li>
</ul>
]]></change-notes>
<depends>com.intellij.modules.platform</depends>
@@ -46,6 +57,21 @@
<add-to-group group-id="DatabaseViewPopupMenu" anchor="first"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt G"/>
</action>
<!-- Defines an action that will appear in the context menu for project directories -->
<action id="com.sdk.generators.actionmodels.GenerateBeanAction.v3"
class="com.sdk.generators.actionmodels.GenerateBeanActionV3"
text="Generate Action Models V3"
description="Generates ActionBean classes from a database schema V3 (sdk.db.xxx).">
<add-to-group group-id="DatabaseViewPopupMenu" anchor="after" relative-to-action="com.sdk.generators.actionmodels.GenerateBeanAction"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt H"/>
</action>
<action id="com.sdk.generators.actionmodels.GenerateDatasetAction"
class="com.sdk.generators.actionmodels.GenerateDatasetAction"
text="Generate Dataset XML"
description="Generates Dataset XML definition from a database table.">
<add-to-group group-id="DatabaseViewPopupMenu" anchor="after" relative-to-action="com.sdk.generators.actionmodels.GenerateBeanAction.v3"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt J"/>
</action>
</actions>
<!-- in plugin.xml, inside the <extensions defaultExtensionNs="com.intellij"> tag -->

View File

@@ -5,7 +5,7 @@ package ${basePackage}.bean;
For Table : ${tableName}
*/
import sdk.dbutils.*;
import ${db_connector};
public class ${tableName} extends ${basePackage}.bean.base.${tableName} {
public ${tableName}(DBConnector connector) { //class construction

View File

@@ -5,7 +5,9 @@ package ${basePackage}.bean.base;
For Table : ${tableName}
*/
import sdk.dbutils.*;
import ${db_connector};
import ${db_dataset};
import sdk.utils.*;
import ${basePackage}.dto.*;

View File

@@ -5,7 +5,7 @@ package ${basePackage}.dto;
For Table : ${tableName}
*/
import sdk.dbutils.*;
import ${db_dto};
public class DTO_${tableName} extends ${basePackage}.dto.base.DTO_${tableName} {
public DTO_${tableName}(DTO dto) { //class construction

View File

@@ -5,7 +5,7 @@ package ${basePackage}.dto.base;
For Table : ${tableName}
*/
import sdk.dbutils.*;
import ${db_dto};
import sdk.utils.*;
public class DTO_${className} extends DTO {

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<DATASET ID="DS-${tableName}">
<SCHEMA>APP</SCHEMA>
<TABLENAME>${tableName}</TABLENAME>
<KEYFIELDS>${keyList}</KEYFIELDS>
<SQL>
<SELECT>SELECT ${fieldList?replace(",", "
, ")}
</SELECT>
<FROM>FROM ${tableName}</FROM>
<ORDER>ORDER BY ${keyList}</ORDER>
</SQL>
<FIELDS>
<#list columns as column>
<FIELD NAME="${column.name}" TYPE="${column.xmlType}" LABEL="${column.label}" WIDTH="${column.width?c}"/>
</#list>
</FIELDS>
</DATASET>