From d501ecec22d12bf6067c597b404a8adc1853f36d Mon Sep 17 00:00:00 2001 From: skidus Date: Fri, 27 Feb 2026 12:33:58 +0700 Subject: [PATCH] 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. --- DevResources/Dataset.xml | 0 build.gradle.kts | 56 ++++++++------ .../actionmodels/GenerateBeanAction.java | 4 +- .../actionmodels/GenerateBeanActionV3.java | 66 +++++++++++++++++ .../actionmodels/GenerateDatasetAction.java | 69 +++++++++++++++++ .../actionmodels/GeneratorServices.java | 74 ++++++++++++++++++- src/main/resources/META-INF/plugin.xml | 66 ++++++++++++----- .../resources/templates/actionBean.extend.ftl | 2 +- src/main/resources/templates/actionBean.ftl | 4 +- .../resources/templates/actionDTO.extend.ftl | 2 +- src/main/resources/templates/actionDTO.ftl | 2 +- src/main/resources/templates/dataset.ftl | 18 +++++ 12 files changed, 313 insertions(+), 50 deletions(-) create mode 100644 DevResources/Dataset.xml create mode 100644 src/main/java/com/sdk/generators/actionmodels/GenerateBeanActionV3.java create mode 100644 src/main/java/com/sdk/generators/actionmodels/GenerateDatasetAction.java create mode 100644 src/main/resources/templates/dataset.ftl diff --git a/DevResources/Dataset.xml b/DevResources/Dataset.xml new file mode 100644 index 0000000..e69de29 diff --git a/build.gradle.kts b/build.gradle.kts index a46d8c3..0808b20 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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. - +

[2.1.0]

+ +

[2.0.1]

+ +

[1.1.2]

+ +

[1.1.1]

+ +

[1.1.0]

+

New Features and Enhancements

+ +

Refactoring and Improvements

+ """ } } diff --git a/src/main/java/com/sdk/generators/actionmodels/GenerateBeanAction.java b/src/main/java/com/sdk/generators/actionmodels/GenerateBeanAction.java index 423d31e..ea31905 100644 --- a/src/main/java/com/sdk/generators/actionmodels/GenerateBeanAction.java +++ b/src/main/java/com/sdk/generators/actionmodels/GenerateBeanAction.java @@ -48,13 +48,13 @@ public class GenerateBeanAction extends AnAction { } private void runGenerator(Project project,ArrayList 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()); } diff --git a/src/main/java/com/sdk/generators/actionmodels/GenerateBeanActionV3.java b/src/main/java/com/sdk/generators/actionmodels/GenerateBeanActionV3.java new file mode 100644 index 0000000..d5d9966 --- /dev/null +++ b/src/main/java/com/sdk/generators/actionmodels/GenerateBeanActionV3.java @@ -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 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 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()); + } + }) + ); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/sdk/generators/actionmodels/GenerateDatasetAction.java b/src/main/java/com/sdk/generators/actionmodels/GenerateDatasetAction.java new file mode 100644 index 0000000..74a1565 --- /dev/null +++ b/src/main/java/com/sdk/generators/actionmodels/GenerateDatasetAction.java @@ -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 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 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()); + } + }) + ); + } + }); + } +} diff --git a/src/main/java/com/sdk/generators/actionmodels/GeneratorServices.java b/src/main/java/com/sdk/generators/actionmodels/GeneratorServices.java index 112f216..60a7fe2 100644 --- a/src/main/java/com/sdk/generators/actionmodels/GeneratorServices.java +++ b/src/main/java/com/sdk/generators/actionmodels/GeneratorServices.java @@ -28,11 +28,15 @@ public class GeneratorServices { private final String basePackage; private final Project project; private final ArrayList tables; + private final Version version; - public GeneratorServices(Project project, ArrayList tables, String basePackage) { + public enum Version {V2,V3} + + public GeneratorServices(Project project, ArrayList tables, String basePackage, Version version) { this.tables = tables; this.project = project; this.basePackage = basePackage; + this.version = version; } private void genDataModel(Template template, Map 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 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 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> columns = new ArrayList<>(); Set primaryKeys = new HashSet<>(); JBIterable 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 dasColumns = DasUtil.getColumns(table); dasColumns.forEach(column -> { Map 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); }); diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index fcf3e2c..25d905e 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -11,26 +11,37 @@ ]]> [2.1.0] +
    +
  • Add: Generate Dataset XML feature to generate XML table definitions from database tables.
  • +
  • Feature: Intelligent label generation for XML datasets, prioritizing database column comments with fallback to entity-based keys.
  • +
  • Feature: Automatic mapping of database types to XML types (TEXT, NUMBER, DATE) and column width extraction.
  • +
  • Shortcut: Added Ctrl + Alt + J for the dataset generation action.
  • +
+

[2.0.1]

+
    +
  • update : Refactory class structure of sdk.dbutils -> sdk.db.xxxx
  • +
  • Add : Generate for new class structure as V3
  • +
+

[1.1.2]

+
    +
  • fix: Handle databases that do not support user-defined types
  • +
+

[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.
  • +
]]>
com.intellij.modules.platform @@ -46,6 +57,21 @@ + + + + + + + + + diff --git a/src/main/resources/templates/actionBean.extend.ftl b/src/main/resources/templates/actionBean.extend.ftl index 4dd7cfe..2c40650 100644 --- a/src/main/resources/templates/actionBean.extend.ftl +++ b/src/main/resources/templates/actionBean.extend.ftl @@ -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 diff --git a/src/main/resources/templates/actionBean.ftl b/src/main/resources/templates/actionBean.ftl index b966520..f56b282 100644 --- a/src/main/resources/templates/actionBean.ftl +++ b/src/main/resources/templates/actionBean.ftl @@ -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.*; diff --git a/src/main/resources/templates/actionDTO.extend.ftl b/src/main/resources/templates/actionDTO.extend.ftl index abc9916..977ced3 100644 --- a/src/main/resources/templates/actionDTO.extend.ftl +++ b/src/main/resources/templates/actionDTO.extend.ftl @@ -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 diff --git a/src/main/resources/templates/actionDTO.ftl b/src/main/resources/templates/actionDTO.ftl index 5b7887b..919e829 100644 --- a/src/main/resources/templates/actionDTO.ftl +++ b/src/main/resources/templates/actionDTO.ftl @@ -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 { diff --git a/src/main/resources/templates/dataset.ftl b/src/main/resources/templates/dataset.ftl new file mode 100644 index 0000000..bb1cb23 --- /dev/null +++ b/src/main/resources/templates/dataset.ftl @@ -0,0 +1,18 @@ + + + APP + ${tableName} + ${keyList} + + + FROM ${tableName} + ORDER BY ${keyList} + + + <#list columns as column> + + + +