From ab74aad2df0d4a3c75841be3659057fb60993eec Mon Sep 17 00:00:00 2001 From: irof Date: Mon, 2 Feb 2026 01:32:49 +0900 Subject: [PATCH 1/4] Add list output tabs for service and repository --- .../adapter/thymeleaf/ListOutputAdapter.java | 95 ++++++- .../resources/templates/assets/list-output.js | 257 +++++++++++++++++- .../main/resources/templates/assets/style.css | 26 ++ .../main/resources/templates/list-output.html | 106 +++++++- jig-core/src/test/js/list-output.test.js | 65 ++++- 5 files changed, 526 insertions(+), 23 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java b/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java index 4869f6b37..e447d27ab 100644 --- a/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java +++ b/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java @@ -4,12 +4,17 @@ import org.dddjava.jig.adapter.JigDocumentWriter; import org.dddjava.jig.application.JigService; import org.dddjava.jig.domain.model.data.members.fields.JigFieldId; +import org.dddjava.jig.domain.model.data.types.JigTypeReference; import org.dddjava.jig.domain.model.data.types.TypeId; import org.dddjava.jig.domain.model.documents.documentformat.JigDocument; import org.dddjava.jig.domain.model.documents.stationery.JigDocumentContext; import org.dddjava.jig.domain.model.information.JigRepository; import org.dddjava.jig.domain.model.information.inputs.Entrypoint; import org.dddjava.jig.domain.model.information.inputs.InputAdapters; +import org.dddjava.jig.domain.model.knowledge.datasource.DatasourceAngle; +import org.dddjava.jig.domain.model.knowledge.datasource.DatasourceAngles; +import org.dddjava.jig.domain.model.knowledge.usecases.ServiceAngles; +import org.dddjava.jig.domain.model.knowledge.usecases.Usecase; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; @@ -35,13 +40,21 @@ public ListOutputAdapter(JigService jigService, TemplateEngine templateEngine, J @HandleDocument(JigDocument.ListOutput) public List invoke(JigRepository repository, JigDocument jigDocument) { InputAdapters inputAdapters = jigService.inputAdapters(repository); + ServiceAngles serviceAngles = jigService.serviceAngles(repository); + DatasourceAngles datasourceAngles = jigService.datasourceAngles(repository); String controllerJson = inputAdapters.listEntrypoint().stream() .map(this::formatControllerJson) .collect(Collectors.joining(",", "[", "]")); + String serviceJson = serviceAngles.list().stream() + .map(this::formatServiceJson) + .collect(Collectors.joining(",", "[", "]")); + String repositoryJson = datasourceAngles.list().stream() + .map(this::formatRepositoryJson) + .collect(Collectors.joining(",", "[", "]")); String listJson = """ - {"controllers": %s} - """.formatted(controllerJson); + {"controllers": %s, "services": %s, "repositories": %s} + """.formatted(controllerJson, serviceJson, repositoryJson); JigDocumentWriter jigDocumentWriter = new JigDocumentWriter(jigDocument, jigDocumentContext.outputDirectory()); Map contextMap = Map.of( @@ -58,13 +71,12 @@ public List invoke(JigRepository repository, JigDocument jigDocument) { } private String formatControllerJson(Entrypoint entrypoint) { - String usingFieldTypesJson = entrypoint.jigMethod().usingFields().jigFieldIds().stream() + List usingFieldTypes = entrypoint.jigMethod().usingFields().jigFieldIds().stream() .map(JigFieldId::declaringTypeId) .map(TypeId::asSimpleText) .sorted() - .map(this::escape) - .map(value -> "\"" + value + "\"") - .collect(Collectors.joining(",", "[", "]")); + .toList(); + String usingFieldTypesText = toJsonStringList(usingFieldTypes); return """ {"packageName": "%s", "typeName": "%s", "methodSignature": "%s", "returnType": "%s", "typeLabel": "%s", "usingFieldTypes": %s, "cyclomaticComplexity": %d, "path": "%s"} """.formatted( @@ -73,11 +85,80 @@ private String formatControllerJson(Entrypoint entrypoint) { escape(entrypoint.jigMethod().simpleMethodSignatureText()), escape(entrypoint.jigMethod().returnType().simpleName()), escape(entrypoint.jigType().label()), - usingFieldTypesJson, + usingFieldTypesText, entrypoint.jigMethod().instructions().cyclomaticComplexity(), escape(entrypoint.fullPathText())); } + private String formatServiceJson(Usecase usecase) { + String usingFieldTypesText = toJsonStringList(usecase.usingFields().jigFieldIds().stream() + .map(JigFieldId::declaringTypeId) + .map(TypeId::asSimpleText) + .sorted() + .toList()); + String parameterTypeLabels = toJsonStringList(usecase.serviceMethod().method().parameterTypeStream() + .map(JigTypeReference::id) + .map(jigDocumentContext::typeTerm) + .map(term -> term.title()) + .toList()); + String usingServiceMethods = toJsonStringList(usecase.usingServiceMethods().stream() + .map(methodCall -> methodCall.asSignatureAndReturnTypeSimpleText()) + .toList()); + String usingRepositoryMethods = toJsonStringList(usecase.usingRepositoryMethods().list().stream() + .map(jigMethod -> jigMethod.simpleMethodSignatureText()) + .toList()); + return """ + {"packageName": "%s", "typeName": "%s", "methodSignature": "%s", "returnType": "%s", "eventHandler": %s, "typeLabel": "%s", "methodLabel": "%s", "returnTypeLabel": "%s", "parameterTypeLabels": %s, "usingFieldTypes": %s, "cyclomaticComplexity": %d, "usingServiceMethods": %s, "usingRepositoryMethods": %s, "useNull": %s, "useStream": %s} + """.formatted( + escape(usecase.serviceMethod().declaringType().packageId().asText()), + escape(usecase.serviceMethod().declaringType().asSimpleText()), + escape(usecase.serviceMethod().method().simpleMethodSignatureText()), + escape(usecase.serviceMethod().method().returnType().simpleName()), + usecase.usingFromController(), + escape(jigDocumentContext.typeTerm(usecase.serviceMethod().declaringType()).title()), + escape(usecase.serviceMethod().method().aliasTextOrBlank()), + escape(jigDocumentContext.typeTerm(usecase.serviceMethod().method().returnType().id()).title()), + parameterTypeLabels, + usingFieldTypesText, + usecase.serviceMethod().method().instructions().cyclomaticComplexity(), + usingServiceMethods, + usingRepositoryMethods, + usecase.useNull(), + usecase.useStream()); + } + + private String formatRepositoryJson(DatasourceAngle datasourceAngle) { + String parameterTypeLabels = toJsonStringList(datasourceAngle.methodParameterTypeStream() + .map(JigTypeReference::id) + .map(jigDocumentContext::typeTerm) + .map(term -> term.title()) + .toList()); + return """ + {"packageName": "%s", "typeName": "%s", "methodSignature": "%s", "returnType": "%s", "typeLabel": "%s", "returnTypeLabel": "%s", "parameterTypeLabels": %s, "cyclomaticComplexity": %d, "insertTables": "%s", "selectTables": "%s", "updateTables": "%s", "deleteTables": "%s", "callerTypeCount": %d, "callerMethodCount": %d} + """.formatted( + escape(datasourceAngle.packageText()), + escape(datasourceAngle.typeSimpleName()), + escape(datasourceAngle.simpleMethodSignatureText()), + escape(datasourceAngle.methodReturnType().simpleNameWithGenerics()), + escape(datasourceAngle.typeLabel()), + escape(jigDocumentContext.typeTerm(datasourceAngle.methodReturnType().id()).title()), + parameterTypeLabels, + datasourceAngle.cyclomaticComplexity(), + escape(datasourceAngle.insertTables()), + escape(datasourceAngle.selectTables()), + escape(datasourceAngle.updateTables()), + escape(datasourceAngle.deleteTables()), + datasourceAngle.callerMethods().typeCount(), + datasourceAngle.callerMethods().size()); + } + + private String toJsonStringList(List values) { + return values.stream() + .map(this::escape) + .map(value -> "\"" + value + "\"") + .collect(Collectors.joining(",", "[", "]")); + } + private String escape(String string) { return string .replace("\\", "\\\\") diff --git a/jig-core/src/main/resources/templates/assets/list-output.js b/jig-core/src/main/resources/templates/assets/list-output.js index c76a4e43e..43b60b0a3 100644 --- a/jig-core/src/main/resources/templates/assets/list-output.js +++ b/jig-core/src/main/resources/templates/assets/list-output.js @@ -9,6 +9,37 @@ function getListData() { * usingFieldTypes: string[], * cyclomaticComplexity: number, * path: string + * }>, services?: Array<{ + * packageName: string, + * typeName: string, + * methodSignature: string, + * returnType: string, + * eventHandler: boolean, + * typeLabel: string, + * methodLabel: string, + * returnTypeLabel: string, + * parameterTypeLabels: string[], + * usingFieldTypes: string[], + * cyclomaticComplexity: number, + * usingServiceMethods: string[], + * usingRepositoryMethods: string[], + * useNull: boolean, + * useStream: boolean + * }>, repositories?: Array<{ + * packageName: string, + * typeName: string, + * methodSignature: string, + * returnType: string, + * typeLabel: string, + * returnTypeLabel: string, + * parameterTypeLabels: string[], + * cyclomaticComplexity: number, + * insertTables: string, + * selectTables: string, + * updateTables: string, + * deleteTables: string, + * callerTypeCount: number, + * callerMethodCount: number * }>} | Array<{ * packageName: string, * typeName: string, @@ -21,9 +52,17 @@ function getListData() { * }>} */ const listData = JSON.parse(jsonText); if (Array.isArray(listData)) { - return listData; + return { + controllers: listData, + services: [], + repositories: [], + }; } - return listData.controllers ?? []; + return { + controllers: listData.controllers ?? [], + services: listData.services ?? [], + repositories: listData.repositories ?? [], + }; } function escapeCsvValue(value) { @@ -41,6 +80,10 @@ function formatFieldTypes(fieldTypes) { return String(fieldTypes); } +function markIfTrue(value) { + return value ? "◯" : ""; +} + function buildControllerCsv(items) { const header = [ "パッケージ名", @@ -66,6 +109,82 @@ function buildControllerCsv(items) { return lines.join("\r\n"); } +function buildServiceCsv(items) { + const header = [ + "パッケージ名", + "クラス名", + "メソッドシグネチャ", + "メソッド戻り値の型", + "イベントハンドラ", + "クラス別名", + "メソッド別名", + "メソッド戻り値の型の別名", + "メソッド引数の型の別名", + "使用しているフィールドの型", + "循環的複雑度", + "使用しているサービスのメソッド", + "使用しているリポジトリのメソッド", + "null使用", + "stream使用", + ]; + const rows = items.map(item => [ + item.packageName ?? "", + item.typeName ?? "", + item.methodSignature ?? "", + item.returnType ?? "", + markIfTrue(item.eventHandler), + item.typeLabel ?? "", + item.methodLabel ?? "", + item.returnTypeLabel ?? "", + formatFieldTypes(item.parameterTypeLabels), + formatFieldTypes(item.usingFieldTypes), + item.cyclomaticComplexity ?? "", + formatFieldTypes(item.usingServiceMethods), + formatFieldTypes(item.usingRepositoryMethods), + markIfTrue(item.useNull), + markIfTrue(item.useStream), + ]); + const lines = [header, ...rows].map(row => row.map(escapeCsvValue).join(",")); + return lines.join("\r\n"); +} + +function buildRepositoryCsv(items) { + const header = [ + "パッケージ名", + "クラス名", + "メソッドシグネチャ", + "メソッド戻り値の型", + "クラス別名", + "メソッド戻り値の型の別名", + "メソッド引数の型の別名", + "循環的複雑度", + "INSERT", + "SELECT", + "UPDATE", + "DELETE", + "関連元クラス数", + "関連元メソッド数", + ]; + const rows = items.map(item => [ + item.packageName ?? "", + item.typeName ?? "", + item.methodSignature ?? "", + item.returnType ?? "", + item.typeLabel ?? "", + item.returnTypeLabel ?? "", + formatFieldTypes(item.parameterTypeLabels), + item.cyclomaticComplexity ?? "", + item.insertTables ?? "", + item.selectTables ?? "", + item.updateTables ?? "", + item.deleteTables ?? "", + item.callerTypeCount ?? "", + item.callerMethodCount ?? "", + ]); + const lines = [header, ...rows].map(row => row.map(escapeCsvValue).join(",")); + return lines.join("\r\n"); +} + function downloadCsv(text, filename) { const blob = new Blob([text], {type: "text/csv;charset=utf-8;"}); const url = URL.createObjectURL(blob); @@ -110,17 +229,131 @@ function renderControllerTable(items) { tableBody.appendChild(fragment); } +function renderServiceTable(items) { + const tableBody = document.querySelector("#service-list tbody"); + if (!tableBody) return; + tableBody.innerHTML = ""; + + const fragment = document.createDocumentFragment(); + items.forEach(item => { + const row = document.createElement("tr"); + const values = [ + item.packageName, + item.typeName, + item.methodSignature, + item.returnType, + markIfTrue(item.eventHandler), + item.typeLabel, + item.methodLabel, + item.returnTypeLabel, + formatFieldTypes(item.parameterTypeLabels), + formatFieldTypes(item.usingFieldTypes), + item.cyclomaticComplexity, + formatFieldTypes(item.usingServiceMethods), + formatFieldTypes(item.usingRepositoryMethods), + markIfTrue(item.useNull), + markIfTrue(item.useStream), + ]; + values.forEach((value, index) => { + const cell = document.createElement("td"); + if (index === 10) { + cell.className = "number"; + } + cell.textContent = value ?? ""; + row.appendChild(cell); + }); + fragment.appendChild(row); + }); + + tableBody.appendChild(fragment); +} + +function renderRepositoryTable(items) { + const tableBody = document.querySelector("#repository-list tbody"); + if (!tableBody) return; + tableBody.innerHTML = ""; + + const fragment = document.createDocumentFragment(); + items.forEach(item => { + const row = document.createElement("tr"); + const values = [ + item.packageName, + item.typeName, + item.methodSignature, + item.returnType, + item.typeLabel, + item.returnTypeLabel, + formatFieldTypes(item.parameterTypeLabels), + item.cyclomaticComplexity, + item.insertTables, + item.selectTables, + item.updateTables, + item.deleteTables, + item.callerTypeCount, + item.callerMethodCount, + ]; + values.forEach((value, index) => { + const cell = document.createElement("td"); + if ([7, 12, 13].includes(index)) { + cell.className = "number"; + } + cell.textContent = value ?? ""; + row.appendChild(cell); + }); + fragment.appendChild(row); + }); + + tableBody.appendChild(fragment); +} + +function activateTab(tabName) { + const tabs = document.querySelectorAll(".list-output-tab"); + const buttons = document.querySelectorAll(".list-output-tabs .tab-button"); + tabs.forEach(tab => { + const isActive = tab.dataset.tab === tabName; + tab.classList.toggle("is-active", isActive); + }); + buttons.forEach(button => { + const isActive = button.dataset.tab === tabName; + button.classList.toggle("is-active", isActive); + button.setAttribute("aria-selected", String(isActive)); + }); +} + if (typeof document !== "undefined") { document.addEventListener("DOMContentLoaded", function () { if (!document.body.classList.contains("list-output")) return; - const items = getListData(); - renderControllerTable(items); - - const exportButton = document.getElementById("export-csv"); - if (exportButton) { - exportButton.addEventListener("click", () => { - const csvText = buildControllerCsv(items); - downloadCsv(csvText, "list-output.csv"); + const data = getListData(); + renderControllerTable(data.controllers); + renderServiceTable(data.services); + renderRepositoryTable(data.repositories); + + const tabButtons = document.querySelectorAll(".list-output-tabs .tab-button"); + tabButtons.forEach(button => { + button.addEventListener("click", () => activateTab(button.dataset.tab)); + }); + + const controllerExportButton = document.getElementById("export-controller-csv"); + if (controllerExportButton) { + controllerExportButton.addEventListener("click", () => { + const csvText = buildControllerCsv(data.controllers); + downloadCsv(csvText, "list-output-controller.csv"); + }); + } + + const serviceExportButton = document.getElementById("export-service-csv"); + if (serviceExportButton) { + serviceExportButton.addEventListener("click", () => { + const csvText = buildServiceCsv(data.services); + downloadCsv(csvText, "list-output-service.csv"); + }); + } + + const repositoryExportButton = document.getElementById("export-repository-csv"); + if (repositoryExportButton) { + repositoryExportButton.addEventListener("click", () => { + const csvText = buildRepositoryCsv(data.repositories); + downloadCsv(csvText, "list-output-repository.csv"); }); } }); @@ -133,6 +366,10 @@ if (typeof module !== "undefined" && module.exports) { escapeCsvValue, formatFieldTypes, buildControllerCsv, + buildServiceCsv, + buildRepositoryCsv, renderControllerTable, + renderServiceTable, + renderRepositoryTable, }; } diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index 8ab1513e7..832446872 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -469,6 +469,32 @@ label { .list-output table thead th { white-space: nowrap; } +.list-output .list-output-tabs { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 1.5rem 0 1rem; +} +.list-output .tab-button { + border: 1px solid #ccc; + background-color: #f4f4f4; + padding: 0.4rem 0.8rem; + cursor: pointer; +} +.list-output .tab-button.is-active { + background-color: #2c2c2c; + border-color: #2c2c2c; + color: #fff; +} +.list-output .list-output-tab { + display: none; +} +.list-output .list-output-tab.is-active { + display: block; +} +.list-output .list-output-actions { + margin-bottom: 0.5rem; +} /* テーブルの行をゼブラスタイルにする */ table.zebra tbody tr:nth-child(odd) { diff --git a/jig-core/src/main/resources/templates/list-output.html b/jig-core/src/main/resources/templates/list-output.html index fc72dbe0b..1453b07e7 100644 --- a/jig-core/src/main/resources/templates/list-output.html +++ b/jig-core/src/main/resources/templates/list-output.html @@ -16,10 +16,16 @@

一覧出力

注意: このドキュメントはincubate(作成中)です。次のバージョンで大きく変更したり削除したりする可能性があります。

-
+
+ + + +
+ +

CONTROLLER

-
- +
+
@@ -37,6 +43,63 @@

CONTROLLER

+ +
+

SERVICE

+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
パッケージ名クラス名メソッドシグネチャメソッド戻り値の型イベントハンドラクラス別名メソッド別名メソッド戻り値の型の別名メソッド引数の型の別名使用しているフィールドの型循環的複雑度使用しているサービスのメソッド使用しているリポジトリのメソッドnull使用stream使用
+
+ +
+

REPOSITORY

+
+ +
+ + + + + + + + + + + + + + + + + + + + +
パッケージ名クラス名メソッドシグネチャメソッド戻り値の型クラス別名メソッド戻り値の型の別名メソッド引数の型の別名循環的複雑度INSERTSELECTUPDATEDELETE関連元クラス数関連元メソッド数
+
diff --git a/jig-core/src/test/js/list-output.test.js b/jig-core/src/test/js/list-output.test.js index f0a3e2a67..fc4fe23cb 100644 --- a/jig-core/src/test/js/list-output.test.js +++ b/jig-core/src/test/js/list-output.test.js @@ -75,6 +75,65 @@ test.describe('list-output.js CSV', () => { '"com.example","ExampleController","getExample()","Example","例","ExampleRepository\nAnotherType","2","GET /example"' ); }); + + test('SERVICEのCSVにヘッダーと行を出力する', () => { + const items = [ + { + packageName: 'com.example', + typeName: 'ExampleService', + methodSignature: 'handle()', + returnType: 'Example', + eventHandler: true, + typeLabel: '例', + methodLabel: '取得', + returnTypeLabel: '例', + parameterTypeLabels: ['Param'], + usingFieldTypes: ['ExampleRepository'], + cyclomaticComplexity: 3, + usingServiceMethods: ['other():Example'], + usingRepositoryMethods: ['find()'], + useNull: false, + useStream: true, + }, + ]; + + const csv = listOutput.buildServiceCsv(items); + + assert.equal( + csv, + '"パッケージ名","クラス名","メソッドシグネチャ","メソッド戻り値の型","イベントハンドラ","クラス別名","メソッド別名","メソッド戻り値の型の別名","メソッド引数の型の別名","使用しているフィールドの型","循環的複雑度","使用しているサービスのメソッド","使用しているリポジトリのメソッド","null使用","stream使用"\r\n' + + '"com.example","ExampleService","handle()","Example","◯","例","取得","例","Param","ExampleRepository","3","other():Example","find()","","◯"' + ); + }); + + test('REPOSITORYのCSVにヘッダーと行を出力する', () => { + const items = [ + { + packageName: 'com.example', + typeName: 'ExampleRepository', + methodSignature: 'find()', + returnType: 'Example', + typeLabel: '例', + returnTypeLabel: '例', + parameterTypeLabels: [], + cyclomaticComplexity: 1, + insertTables: 'EXAMPLE', + selectTables: 'EXAMPLE', + updateTables: '', + deleteTables: '', + callerTypeCount: 1, + callerMethodCount: 2, + }, + ]; + + const csv = listOutput.buildRepositoryCsv(items); + + assert.equal( + csv, + '"パッケージ名","クラス名","メソッドシグネチャ","メソッド戻り値の型","クラス別名","メソッド戻り値の型の別名","メソッド引数の型の別名","循環的複雑度","INSERT","SELECT","UPDATE","DELETE","関連元クラス数","関連元メソッド数"\r\n' + + '"com.example","ExampleRepository","find()","Example","例","例","","1","EXAMPLE","EXAMPLE","","","1","2"' + ); + }); }); test.describe('list-output.js データ読み込み', () => { @@ -86,10 +145,10 @@ test.describe('list-output.js データ読み込み', () => { }); doc.elementsById.set('list-data', dataElement); - const items = listOutput.getListData(); + const data = listOutput.getListData(); - assert.equal(items.length, 1); - assert.equal(items[0].typeName, 'ExampleController'); + assert.equal(data.controllers.length, 1); + assert.equal(data.controllers[0].typeName, 'ExampleController'); }); }); From 02ea8febfae24f14e82968f3f23e1aecd7eb10a9 Mon Sep 17 00:00:00 2001 From: irof Date: Mon, 2 Feb 2026 01:41:49 +0900 Subject: [PATCH 2/4] Store repository tables as arrays in list output --- .../adapter/thymeleaf/ListOutputAdapter.java | 14 +++++++---- .../knowledge/datasource/DatasourceAngle.java | 24 +++++++++++++++++++ .../resources/templates/assets/list-output.js | 24 +++++++++---------- .../main/resources/templates/list-output.html | 8 +++---- jig-core/src/test/js/list-output.test.js | 8 +++---- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java b/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java index e447d27ab..d479df302 100644 --- a/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java +++ b/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java @@ -133,8 +133,12 @@ private String formatRepositoryJson(DatasourceAngle datasourceAngle) { .map(jigDocumentContext::typeTerm) .map(term -> term.title()) .toList()); + String insertTables = toJsonStringList(datasourceAngle.insertTableNames()); + String selectTables = toJsonStringList(datasourceAngle.selectTableNames()); + String updateTables = toJsonStringList(datasourceAngle.updateTableNames()); + String deleteTables = toJsonStringList(datasourceAngle.deleteTableNames()); return """ - {"packageName": "%s", "typeName": "%s", "methodSignature": "%s", "returnType": "%s", "typeLabel": "%s", "returnTypeLabel": "%s", "parameterTypeLabels": %s, "cyclomaticComplexity": %d, "insertTables": "%s", "selectTables": "%s", "updateTables": "%s", "deleteTables": "%s", "callerTypeCount": %d, "callerMethodCount": %d} + {"packageName": "%s", "typeName": "%s", "methodSignature": "%s", "returnType": "%s", "typeLabel": "%s", "returnTypeLabel": "%s", "parameterTypeLabels": %s, "cyclomaticComplexity": %d, "insertTables": %s, "selectTables": %s, "updateTables": %s, "deleteTables": %s, "callerTypeCount": %d, "callerMethodCount": %d} """.formatted( escape(datasourceAngle.packageText()), escape(datasourceAngle.typeSimpleName()), @@ -144,10 +148,10 @@ private String formatRepositoryJson(DatasourceAngle datasourceAngle) { escape(jigDocumentContext.typeTerm(datasourceAngle.methodReturnType().id()).title()), parameterTypeLabels, datasourceAngle.cyclomaticComplexity(), - escape(datasourceAngle.insertTables()), - escape(datasourceAngle.selectTables()), - escape(datasourceAngle.updateTables()), - escape(datasourceAngle.deleteTables()), + insertTables, + selectTables, + updateTables, + deleteTables, datasourceAngle.callerMethods().typeCount(), datasourceAngle.callerMethods().size()); } diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngle.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngle.java index 194e4da68..767d83d4b 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngle.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngle.java @@ -52,18 +52,34 @@ public String insertTables() { return crudTables.create().asText(); } + public java.util.List insertTableNames() { + return tableNames(crudTables.create()); + } + public String selectTables() { return crudTables.read().asText(); } + public java.util.List selectTableNames() { + return tableNames(crudTables.read()); + } + public String updateTables() { return crudTables.update().asText(); } + public java.util.List updateTableNames() { + return tableNames(crudTables.update()); + } + public String deleteTables() { return crudTables.delete().asText(); } + public java.util.List deleteTableNames() { + return tableNames(crudTables.delete()); + } + public JigMethod concreteMethod() { return outputImplementation.concreteMethod(); } @@ -83,4 +99,12 @@ public String typeSimpleName() { public String typeLabel() { return outputImplementation.interfaceJigType().label(); } + + private java.util.List tableNames(org.dddjava.jig.domain.model.data.rdbaccess.Tables tables) { + return tables.tables().stream() + .map(org.dddjava.jig.domain.model.data.rdbaccess.Table::name) + .distinct() + .sorted() + .toList(); + } } diff --git a/jig-core/src/main/resources/templates/assets/list-output.js b/jig-core/src/main/resources/templates/assets/list-output.js index 43b60b0a3..f9021328f 100644 --- a/jig-core/src/main/resources/templates/assets/list-output.js +++ b/jig-core/src/main/resources/templates/assets/list-output.js @@ -34,10 +34,10 @@ function getListData() { * returnTypeLabel: string, * parameterTypeLabels: string[], * cyclomaticComplexity: number, - * insertTables: string, - * selectTables: string, - * updateTables: string, - * deleteTables: string, + * insertTables: string[], + * selectTables: string[], + * updateTables: string[], + * deleteTables: string[], * callerTypeCount: number, * callerMethodCount: number * }>} | Array<{ @@ -174,10 +174,10 @@ function buildRepositoryCsv(items) { item.returnTypeLabel ?? "", formatFieldTypes(item.parameterTypeLabels), item.cyclomaticComplexity ?? "", - item.insertTables ?? "", - item.selectTables ?? "", - item.updateTables ?? "", - item.deleteTables ?? "", + formatFieldTypes(item.insertTables), + formatFieldTypes(item.selectTables), + formatFieldTypes(item.updateTables), + formatFieldTypes(item.deleteTables), item.callerTypeCount ?? "", item.callerMethodCount ?? "", ]); @@ -285,10 +285,10 @@ function renderRepositoryTable(items) { item.returnTypeLabel, formatFieldTypes(item.parameterTypeLabels), item.cyclomaticComplexity, - item.insertTables, - item.selectTables, - item.updateTables, - item.deleteTables, + formatFieldTypes(item.insertTables), + formatFieldTypes(item.selectTables), + formatFieldTypes(item.updateTables), + formatFieldTypes(item.deleteTables), item.callerTypeCount, item.callerMethodCount, ]; diff --git a/jig-core/src/main/resources/templates/list-output.html b/jig-core/src/main/resources/templates/list-output.html index 1453b07e7..d516d9293 100644 --- a/jig-core/src/main/resources/templates/list-output.html +++ b/jig-core/src/main/resources/templates/list-output.html @@ -145,10 +145,10 @@

REPOSITORY

"returnTypeLabel": "例", "parameterTypeLabels": [], "cyclomaticComplexity": 1, - "insertTables": "EXAMPLE", - "selectTables": "EXAMPLE", - "updateTables": "", - "deleteTables": "", + "insertTables": ["EXAMPLE"], + "selectTables": ["EXAMPLE"], + "updateTables": [], + "deleteTables": [], "callerTypeCount": 1, "callerMethodCount": 1 } diff --git a/jig-core/src/test/js/list-output.test.js b/jig-core/src/test/js/list-output.test.js index fc4fe23cb..b4c43ee0a 100644 --- a/jig-core/src/test/js/list-output.test.js +++ b/jig-core/src/test/js/list-output.test.js @@ -117,10 +117,10 @@ test.describe('list-output.js CSV', () => { returnTypeLabel: '例', parameterTypeLabels: [], cyclomaticComplexity: 1, - insertTables: 'EXAMPLE', - selectTables: 'EXAMPLE', - updateTables: '', - deleteTables: '', + insertTables: ['EXAMPLE'], + selectTables: ['EXAMPLE'], + updateTables: [], + deleteTables: [], callerTypeCount: 1, callerMethodCount: 2, }, From 7b5880341f1413895ad7d120ee0dea1ac9874fbf Mon Sep 17 00:00:00 2001 From: irof Date: Tue, 3 Feb 2026 00:51:50 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E4=B8=80=E8=A6=A7=E3=81=A7=E5=87=BA?= =?UTF-8?q?=E5=8A=9B=E3=81=97=E3=81=A6=E3=82=8B=E3=81=AE=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/thymeleaf/ListOutputAdapter.java | 163 +++++- .../resources/templates/assets/list-output.js | 528 +++++++++++++++--- .../main/resources/templates/list-output.html | 478 ++++++++++++---- jig-core/src/test/js/list-output.test.js | 12 +- 4 files changed, 983 insertions(+), 198 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java b/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java index d479df302..8a315f66e 100644 --- a/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java +++ b/jig-core/src/main/java/org/dddjava/jig/adapter/thymeleaf/ListOutputAdapter.java @@ -2,31 +2,49 @@ import org.dddjava.jig.adapter.HandleDocument; import org.dddjava.jig.adapter.JigDocumentWriter; +import org.dddjava.jig.application.CoreTypesAndRelations; import org.dddjava.jig.application.JigService; import org.dddjava.jig.domain.model.data.members.fields.JigFieldId; +import org.dddjava.jig.domain.model.data.packages.PackageId; import org.dddjava.jig.domain.model.data.types.JigTypeReference; +import org.dddjava.jig.domain.model.data.types.JigTypeVisibility; import org.dddjava.jig.domain.model.data.types.TypeId; import org.dddjava.jig.domain.model.documents.documentformat.JigDocument; import org.dddjava.jig.domain.model.documents.stationery.JigDocumentContext; import org.dddjava.jig.domain.model.information.JigRepository; import org.dddjava.jig.domain.model.information.inputs.Entrypoint; import org.dddjava.jig.domain.model.information.inputs.InputAdapters; +import org.dddjava.jig.domain.model.information.members.JigMethod; +import org.dddjava.jig.domain.model.information.relation.types.TypeRelationships; +import org.dddjava.jig.domain.model.information.types.JigType; +import org.dddjava.jig.domain.model.information.types.JigTypes; +import org.dddjava.jig.domain.model.information.types.TypeKind; import org.dddjava.jig.domain.model.knowledge.datasource.DatasourceAngle; import org.dddjava.jig.domain.model.knowledge.datasource.DatasourceAngles; +import org.dddjava.jig.domain.model.knowledge.module.JigPackage; +import org.dddjava.jig.domain.model.knowledge.module.JigPackages; +import org.dddjava.jig.domain.model.knowledge.smell.MethodSmell; +import org.dddjava.jig.domain.model.knowledge.smell.MethodSmells; import org.dddjava.jig.domain.model.knowledge.usecases.ServiceAngles; import org.dddjava.jig.domain.model.knowledge.usecases.Usecase; +import org.dddjava.jig.domain.model.knowledge.validations.Validation; +import org.dddjava.jig.domain.model.knowledge.validations.Validations; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import java.nio.file.Path; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; +import java.util.stream.Collector; import java.util.stream.Collectors; @HandleDocument public class ListOutputAdapter { + /** + * 一覧出力で複数要素を文字列連結する際のコレクター + */ + private static final Collector STREAM_COLLECTOR = Collectors.joining(", ", "[", "]"); + private final JigService jigService; private final TemplateEngine templateEngine; private final JigDocumentContext jigDocumentContext; @@ -42,6 +60,21 @@ public List invoke(JigRepository repository, JigDocument jigDocument) { InputAdapters inputAdapters = jigService.inputAdapters(repository); ServiceAngles serviceAngles = jigService.serviceAngles(repository); DatasourceAngles datasourceAngles = jigService.datasourceAngles(repository); + MethodSmells methodSmells = jigService.methodSmells(repository); + JigTypes jigTypes = jigService.jigTypes(repository); + TypeRelationships allClassRelations = TypeRelationships.from(jigTypes); + CoreTypesAndRelations coreTypesAndRelations = jigService.coreTypesAndRelations(repository); + JigTypes coreDomainJigTypes = coreTypesAndRelations.coreJigTypes(); + JigTypes categoryTypes = jigService.categoryTypes(repository); + + JigPackages packages = jigService.packages(repository); + Set coreDomainPackages = coreDomainJigTypes.stream() + .map(JigType::packageId) + .collect(Collectors.toUnmodifiableSet()); + List jigTypePackages = packages.jigPackages().stream() + .filter(jigPackage -> coreDomainPackages.contains(jigPackage.packageId())) + .sorted(Comparator.comparing(JigPackage::packageId)) + .toList(); String controllerJson = inputAdapters.listEntrypoint().stream() .map(this::formatControllerJson) .collect(Collectors.joining(",", "[", "]")); @@ -51,10 +84,28 @@ public List invoke(JigRepository repository, JigDocument jigDocument) { String repositoryJson = datasourceAngles.list().stream() .map(this::formatRepositoryJson) .collect(Collectors.joining(",", "[", "]")); + String packageJson = jigTypePackages.stream() + .map(this::formatBusinessPackageJson) + .collect(Collectors.joining(",", "[", "]")); + String allJson = coreDomainJigTypes.list().stream() + .map(jigType -> formatBusinessAllJson(jigType, coreTypesAndRelations, allClassRelations)) + .collect(Collectors.joining(",", "[", "]")); + String enumJson = categoryTypes.list().stream() + .map(jigType -> formatBusinessEnumJson(jigType, allClassRelations)) + .collect(Collectors.joining(",", "[", "]")); + String collectionJson = coreDomainJigTypes.listCollectionType().stream() + .map(jigType -> formatBusinessCollectionJson(jigType, allClassRelations)) + .collect(Collectors.joining(",", "[", "]")); + String validationJson = Validations.from(jigTypes).list().stream() + .map(this::formatBusinessValidationJson) + .collect(Collectors.joining(",", "[", "]")); + String methodSmellJson = methodSmells.list().stream() + .map(this::formatBusinessMethodSmellJson) + .collect(Collectors.joining(",", "[", "]")); String listJson = """ - {"controllers": %s, "services": %s, "repositories": %s} - """.formatted(controllerJson, serviceJson, repositoryJson); + {"businessRules": {"packages": %s, "all": %s, "enums": %s, "collections": %s, "validations": %s, "methodSmells": %s}, "applications": {"controllers": %s, "services": %s, "repositories": %s}} + """.formatted(packageJson, allJson, enumJson, collectionJson, validationJson, methodSmellJson, controllerJson, serviceJson, repositoryJson); JigDocumentWriter jigDocumentWriter = new JigDocumentWriter(jigDocument, jigDocumentContext.outputDirectory()); Map contextMap = Map.of( @@ -156,6 +207,108 @@ private String formatRepositoryJson(DatasourceAngle datasourceAngle) { datasourceAngle.callerMethods().size()); } + private String formatBusinessPackageJson(JigPackage jigPackage) { + return """ + {"packageName": "%s", "packageLabel": "%s", "classCount": %d} + """.formatted( + escape(jigPackage.packageId().asText()), + escape(jigPackage.term().title()), + jigPackage.jigTypes().size()); + } + + private String formatBusinessAllJson(JigType jigType, CoreTypesAndRelations coreTypesAndRelations, TypeRelationships allClassRelations) { + boolean samePackageOnly = allClassRelations.collectTypeIdWhichRelationTo(jigType.id()).packageIds().values() + .equals(Set.of(jigType.packageId())); + return """ + {"packageName": "%s", "typeName": "%s", "typeLabel": "%s", "businessRuleKind": "%s", "incomingBusinessRuleCount": %d, "outgoingBusinessRuleCount": %d, "incomingClassCount": %d, "nonPublic": %s, "samePackageOnly": %s, "incomingClassList": "%s"} + """.formatted( + escape(jigType.packageId().asText()), + escape(jigType.id().asSimpleText()), + escape(jigType.label()), + escape(jigType.toValueKind().toString()), + coreTypesAndRelations.internalTypeRelationships().filterTo(jigType.id()).size(), + coreTypesAndRelations.internalTypeRelationships().filterFrom(jigType.id()).size(), + allClassRelations.collectTypeIdWhichRelationTo(jigType.id()).list().size(), + jigType.visibility() != JigTypeVisibility.PUBLIC, + samePackageOnly, + escape(allClassRelations.collectTypeIdWhichRelationTo(jigType.id()).asSimpleText())); + } + + private String formatBusinessEnumJson(JigType jigType, TypeRelationships allClassRelations) { + String constants = jigType.jigTypeMembers().enumConstantStream() + .map(jigField -> jigField.jigFieldHeader().name()) + .collect(STREAM_COLLECTOR); + String fields = jigType.jigTypeMembers().instanceFields().stream() + .map(jigField -> jigField.jigFieldHeader().simpleText()) + .collect(STREAM_COLLECTOR); + return """ + {"packageName": "%s", "typeName": "%s", "typeLabel": "%s", "constants": "%s", "fields": "%s", "usageCount": %d, "usagePlaces": "%s", "hasParameters": %s, "hasBehavior": %s, "isPolymorphic": %s} + """.formatted( + escape(jigType.packageId().asText()), + escape(jigType.id().asSimpleText()), + escape(jigType.label()), + escape(constants), + escape(fields), + allClassRelations.collectTypeIdWhichRelationTo(jigType.id()).list().size(), + escape(allClassRelations.collectTypeIdWhichRelationTo(jigType.id()).asSimpleText()), + jigType.hasInstanceField(), + jigType.hasInstanceMethod(), + jigType.typeKind() == TypeKind.抽象列挙型); + } + + private String formatBusinessCollectionJson(JigType jigType, TypeRelationships allClassRelations) { + List fieldTypeList = jigType.jigTypeMembers().instanceFields().stream() + .map(jigField -> jigField.jigTypeReference().simpleNameWithGenerics()) + .toList(); + String fieldTypes = fieldTypeList.size() == 1 + ? fieldTypeList.get(0) + : fieldTypeList.stream().collect(STREAM_COLLECTOR); + return """ + {"packageName": "%s", "typeName": "%s", "typeLabel": "%s", "fieldTypes": "%s", "usageCount": %d, "usagePlaces": "%s", "methodCount": %d, "methods": "%s"} + """.formatted( + escape(jigType.packageId().asText()), + escape(jigType.id().asSimpleText()), + escape(jigType.label()), + escape(fieldTypes), + allClassRelations.collectTypeIdWhichRelationTo(jigType.id()).size(), + escape(allClassRelations.collectTypeIdWhichRelationTo(jigType.id()).asSimpleText()), + jigType.instanceJigMethods().list().size(), + escape(jigType.instanceJigMethods().stream() + .map(JigMethod::simpleMethodDeclarationText) + .sorted() + .collect(STREAM_COLLECTOR))); + } + + private String formatBusinessValidationJson(Validation validation) { + return """ + {"packageName": "%s", "typeName": "%s", "typeLabel": "%s", "memberName": "%s", "memberType": "%s", "annotationType": "%s", "annotationDescription": "%s"} + """.formatted( + escape(validation.typeId().packageId().asText()), + escape(validation.typeId().asSimpleText()), + escape(jigDocumentContext.typeTerm(validation.typeId()).title()), + escape(validation.memberName()), + escape(validation.memberType().asSimpleText()), + escape(validation.annotationType().asSimpleText()), + escape(validation.annotationDescription())); + } + + private String formatBusinessMethodSmellJson(MethodSmell methodSmell) { + return """ + {"packageName": "%s", "typeName": "%s", "methodSignature": "%s", "returnType": "%s", "typeLabel": "%s", "notUseMember": %s, "primitiveInterface": %s, "referenceNull": %s, "nullDecision": %s, "returnsBoolean": %s, "returnsVoid": %s} + """.formatted( + escape(methodSmell.method().declaringType().packageId().asText()), + escape(methodSmell.method().declaringType().asSimpleText()), + escape(methodSmell.method().simpleMethodSignatureText()), + escape(methodSmell.methodReturnType().asSimpleText()), + escape(methodSmell.declaringJigType().label()), + methodSmell.notUseMember(), + methodSmell.primitiveInterface(), + methodSmell.referenceNull(), + methodSmell.nullDecision(), + methodSmell.returnsBoolean(), + methodSmell.returnsVoid()); + } + private String toJsonStringList(List values) { return values.stream() .map(this::escape) diff --git a/jig-core/src/main/resources/templates/assets/list-output.js b/jig-core/src/main/resources/templates/assets/list-output.js index f9021328f..402422365 100644 --- a/jig-core/src/main/resources/templates/assets/list-output.js +++ b/jig-core/src/main/resources/templates/assets/list-output.js @@ -1,67 +1,62 @@ function getListData() { const jsonText = document.getElementById("list-data")?.textContent || "{}"; - /** @type {{controllers?: Array<{ - * packageName: string, - * typeName: string, - * methodSignature: string, - * returnType: string, - * typeLabel: string, - * usingFieldTypes: string[], - * cyclomaticComplexity: number, - * path: string - * }>, services?: Array<{ - * packageName: string, - * typeName: string, - * methodSignature: string, - * returnType: string, - * eventHandler: boolean, - * typeLabel: string, - * methodLabel: string, - * returnTypeLabel: string, - * parameterTypeLabels: string[], - * usingFieldTypes: string[], - * cyclomaticComplexity: number, - * usingServiceMethods: string[], - * usingRepositoryMethods: string[], - * useNull: boolean, - * useStream: boolean - * }>, repositories?: Array<{ - * packageName: string, - * typeName: string, - * methodSignature: string, - * returnType: string, - * typeLabel: string, - * returnTypeLabel: string, - * parameterTypeLabels: string[], - * cyclomaticComplexity: number, - * insertTables: string[], - * selectTables: string[], - * updateTables: string[], - * deleteTables: string[], - * callerTypeCount: number, - * callerMethodCount: number - * }>} | Array<{ - * packageName: string, - * typeName: string, - * methodSignature: string, - * returnType: string, - * typeLabel: string, - * usingFieldTypes: string[], - * cyclomaticComplexity: number, - * path: string - * }>} */ + /** @type {{ + * businessRules?: { + * packages?: Array<{packageName: string, packageLabel: string, classCount: number}>, + * all?: Array<{packageName: string, typeName: string, typeLabel: string, businessRuleKind: string, incomingBusinessRuleCount: number, outgoingBusinessRuleCount: number, incomingClassCount: number, nonPublic: boolean, samePackageOnly: boolean, incomingClassList: string}>, + * enums?: Array<{packageName: string, typeName: string, typeLabel: string, constants: string, fields: string, usageCount: number, usagePlaces: string, hasParameters: boolean, hasBehavior: boolean, isPolymorphic: boolean}>, + * collections?: Array<{packageName: string, typeName: string, typeLabel: string, fieldTypes: string, usageCount: number, usagePlaces: string, methodCount: number, methods: string}>, + * validations?: Array<{packageName: string, typeName: string, typeLabel: string, memberName: string, memberType: string, annotationType: string, annotationDescription: string}>, + * methodSmells?: Array<{packageName: string, typeName: string, methodSignature: string, returnType: string, typeLabel: string, notUseMember: boolean, primitiveInterface: boolean, referenceNull: boolean, nullDecision: boolean, returnsBoolean: boolean, returnsVoid: boolean}> + * }, + * applications?: { + * controllers?: Array<{packageName: string, typeName: string, methodSignature: string, returnType: string, typeLabel: string, usingFieldTypes: string[], cyclomaticComplexity: number, path: string}>, + * services?: Array<{packageName: string, typeName: string, methodSignature: string, returnType: string, eventHandler: boolean, typeLabel: string, methodLabel: string, returnTypeLabel: string, parameterTypeLabels: string[], usingFieldTypes: string[], cyclomaticComplexity: number, usingServiceMethods: string[], usingRepositoryMethods: string[], useNull: boolean, useStream: boolean}>, + * repositories?: Array<{packageName: string, typeName: string, methodSignature: string, returnType: string, typeLabel: string, returnTypeLabel: string, parameterTypeLabels: string[], cyclomaticComplexity: number, insertTables: string[], selectTables: string[], updateTables: string[], deleteTables: string[], callerTypeCount: number, callerMethodCount: number}> + * } + * } | Array<{packageName: string, typeName: string, methodSignature: string, returnType: string, typeLabel: string, usingFieldTypes: string[], cyclomaticComplexity: number, path: string}> */ const listData = JSON.parse(jsonText); + const emptyBusinessRules = { + packages: [], + all: [], + enums: [], + collections: [], + validations: [], + methodSmells: [], + }; + const emptyApplications = { + controllers: [], + services: [], + repositories: [], + }; if (Array.isArray(listData)) { return { - controllers: listData, - services: [], - repositories: [], + businessRules: emptyBusinessRules, + applications: { + ...emptyApplications, + controllers: listData, + }, + }; + } + if (listData.businessRules || listData.applications) { + return { + businessRules: { + ...emptyBusinessRules, + ...(listData.businessRules ?? {}), + }, + applications: { + ...emptyApplications, + ...(listData.applications ?? {}), + }, }; } return { - controllers: listData.controllers ?? [], - services: listData.services ?? [], - repositories: listData.repositories ?? [], + businessRules: emptyBusinessRules, + applications: { + controllers: listData.controllers ?? [], + services: listData.services ?? [], + repositories: listData.repositories ?? [], + }, }; } @@ -185,6 +180,154 @@ function buildRepositoryCsv(items) { return lines.join("\r\n"); } +function buildBusinessPackageCsv(items) { + const header = ["パッケージ名", "パッケージ別名", "クラス数"]; + const rows = items.map(item => [ + item.packageName ?? "", + item.packageLabel ?? "", + item.classCount ?? "", + ]); + const lines = [header, ...rows].map(row => row.map(escapeCsvValue).join(",")); + return lines.join("\r\n"); +} + +function buildBusinessAllCsv(items) { + const header = [ + "パッケージ名", + "クラス名", + "クラス別名", + "ビジネスルールの種類", + "関連元ビジネスルール数", + "関連先ビジネスルール数", + "関連元クラス数", + "非PUBLIC", + "同パッケージからのみ参照", + "関連元クラス", + ]; + const rows = items.map(item => [ + item.packageName ?? "", + item.typeName ?? "", + item.typeLabel ?? "", + item.businessRuleKind ?? "", + item.incomingBusinessRuleCount ?? "", + item.outgoingBusinessRuleCount ?? "", + item.incomingClassCount ?? "", + markIfTrue(item.nonPublic), + markIfTrue(item.samePackageOnly), + item.incomingClassList ?? "", + ]); + const lines = [header, ...rows].map(row => row.map(escapeCsvValue).join(",")); + return lines.join("\r\n"); +} + +function buildBusinessEnumCsv(items) { + const header = [ + "パッケージ名", + "クラス名", + "クラス別名", + "定数宣言", + "フィールド", + "使用箇所数", + "使用箇所", + "パラメーター有り", + "振る舞い有り", + "多態", + ]; + const rows = items.map(item => [ + item.packageName ?? "", + item.typeName ?? "", + item.typeLabel ?? "", + item.constants ?? "", + item.fields ?? "", + item.usageCount ?? "", + item.usagePlaces ?? "", + markIfTrue(item.hasParameters), + markIfTrue(item.hasBehavior), + markIfTrue(item.isPolymorphic), + ]); + const lines = [header, ...rows].map(row => row.map(escapeCsvValue).join(",")); + return lines.join("\r\n"); +} + +function buildBusinessCollectionCsv(items) { + const header = [ + "パッケージ名", + "クラス名", + "クラス別名", + "フィールドの型", + "使用箇所数", + "使用箇所", + "メソッド数", + "メソッド一覧", + ]; + const rows = items.map(item => [ + item.packageName ?? "", + item.typeName ?? "", + item.typeLabel ?? "", + item.fieldTypes ?? "", + item.usageCount ?? "", + item.usagePlaces ?? "", + item.methodCount ?? "", + item.methods ?? "", + ]); + const lines = [header, ...rows].map(row => row.map(escapeCsvValue).join(",")); + return lines.join("\r\n"); +} + +function buildBusinessValidationCsv(items) { + const header = [ + "パッケージ名", + "クラス名", + "クラス別名", + "メンバ名", + "メンバクラス名", + "アノテーションクラス名", + "アノテーション記述", + ]; + const rows = items.map(item => [ + item.packageName ?? "", + item.typeName ?? "", + item.typeLabel ?? "", + item.memberName ?? "", + item.memberType ?? "", + item.annotationType ?? "", + item.annotationDescription ?? "", + ]); + const lines = [header, ...rows].map(row => row.map(escapeCsvValue).join(",")); + return lines.join("\r\n"); +} + +function buildBusinessSmellCsv(items) { + const header = [ + "パッケージ名", + "クラス名", + "メソッドシグネチャ", + "メソッド戻り値の型", + "クラス別名", + "メンバを使用していない", + "基本型の授受を行なっている", + "NULLリテラルを使用している", + "NULL判定をしている", + "真偽値を返している", + "voidを返している", + ]; + const rows = items.map(item => [ + item.packageName ?? "", + item.typeName ?? "", + item.methodSignature ?? "", + item.returnType ?? "", + item.typeLabel ?? "", + markIfTrue(item.notUseMember), + markIfTrue(item.primitiveInterface), + markIfTrue(item.referenceNull), + markIfTrue(item.nullDecision), + markIfTrue(item.returnsBoolean), + markIfTrue(item.returnsVoid), + ]); + const lines = [header, ...rows].map(row => row.map(escapeCsvValue).join(",")); + return lines.join("\r\n"); +} + function downloadCsv(text, filename) { const blob = new Blob([text], {type: "text/csv;charset=utf-8;"}); const url = URL.createObjectURL(blob); @@ -306,9 +449,196 @@ function renderRepositoryTable(items) { tableBody.appendChild(fragment); } -function activateTab(tabName) { - const tabs = document.querySelectorAll(".list-output-tab"); - const buttons = document.querySelectorAll(".list-output-tabs .tab-button"); +function renderBusinessPackageTable(items) { + const tableBody = document.querySelector("#business-package-list tbody"); + if (!tableBody) return; + tableBody.innerHTML = ""; + + const fragment = document.createDocumentFragment(); + items.forEach(item => { + const row = document.createElement("tr"); + const values = [ + item.packageName, + item.packageLabel, + item.classCount, + ]; + values.forEach((value, index) => { + const cell = document.createElement("td"); + if (index === 2) { + cell.className = "number"; + } + cell.textContent = value ?? ""; + row.appendChild(cell); + }); + fragment.appendChild(row); + }); + + tableBody.appendChild(fragment); +} + +function renderBusinessAllTable(items) { + const tableBody = document.querySelector("#business-all-list tbody"); + if (!tableBody) return; + tableBody.innerHTML = ""; + + const fragment = document.createDocumentFragment(); + items.forEach(item => { + const row = document.createElement("tr"); + const values = [ + item.packageName, + item.typeName, + item.typeLabel, + item.businessRuleKind, + item.incomingBusinessRuleCount, + item.outgoingBusinessRuleCount, + item.incomingClassCount, + markIfTrue(item.nonPublic), + markIfTrue(item.samePackageOnly), + item.incomingClassList, + ]; + values.forEach((value, index) => { + const cell = document.createElement("td"); + if ([4, 5, 6].includes(index)) { + cell.className = "number"; + } + cell.textContent = value ?? ""; + row.appendChild(cell); + }); + fragment.appendChild(row); + }); + + tableBody.appendChild(fragment); +} + +function renderBusinessEnumTable(items) { + const tableBody = document.querySelector("#business-enum-list tbody"); + if (!tableBody) return; + tableBody.innerHTML = ""; + + const fragment = document.createDocumentFragment(); + items.forEach(item => { + const row = document.createElement("tr"); + const values = [ + item.packageName, + item.typeName, + item.typeLabel, + item.constants, + item.fields, + item.usageCount, + item.usagePlaces, + markIfTrue(item.hasParameters), + markIfTrue(item.hasBehavior), + markIfTrue(item.isPolymorphic), + ]; + values.forEach((value, index) => { + const cell = document.createElement("td"); + if (index === 5) { + cell.className = "number"; + } + cell.textContent = value ?? ""; + row.appendChild(cell); + }); + fragment.appendChild(row); + }); + + tableBody.appendChild(fragment); +} + +function renderBusinessCollectionTable(items) { + const tableBody = document.querySelector("#business-collection-list tbody"); + if (!tableBody) return; + tableBody.innerHTML = ""; + + const fragment = document.createDocumentFragment(); + items.forEach(item => { + const row = document.createElement("tr"); + const values = [ + item.packageName, + item.typeName, + item.typeLabel, + item.fieldTypes, + item.usageCount, + item.usagePlaces, + item.methodCount, + item.methods, + ]; + values.forEach((value, index) => { + const cell = document.createElement("td"); + if ([4, 6].includes(index)) { + cell.className = "number"; + } + cell.textContent = value ?? ""; + row.appendChild(cell); + }); + fragment.appendChild(row); + }); + + tableBody.appendChild(fragment); +} + +function renderBusinessValidationTable(items) { + const tableBody = document.querySelector("#business-validation-list tbody"); + if (!tableBody) return; + tableBody.innerHTML = ""; + + const fragment = document.createDocumentFragment(); + items.forEach(item => { + const row = document.createElement("tr"); + const values = [ + item.packageName, + item.typeName, + item.typeLabel, + item.memberName, + item.memberType, + item.annotationType, + item.annotationDescription, + ]; + values.forEach(value => { + const cell = document.createElement("td"); + cell.textContent = value ?? ""; + row.appendChild(cell); + }); + fragment.appendChild(row); + }); + + tableBody.appendChild(fragment); +} + +function renderBusinessSmellTable(items) { + const tableBody = document.querySelector("#business-smell-list tbody"); + if (!tableBody) return; + tableBody.innerHTML = ""; + + const fragment = document.createDocumentFragment(); + items.forEach(item => { + const row = document.createElement("tr"); + const values = [ + item.packageName, + item.typeName, + item.methodSignature, + item.returnType, + item.typeLabel, + markIfTrue(item.notUseMember), + markIfTrue(item.primitiveInterface), + markIfTrue(item.referenceNull), + markIfTrue(item.nullDecision), + markIfTrue(item.returnsBoolean), + markIfTrue(item.returnsVoid), + ]; + values.forEach(value => { + const cell = document.createElement("td"); + cell.textContent = value ?? ""; + row.appendChild(cell); + }); + fragment.appendChild(row); + }); + + tableBody.appendChild(fragment); +} + +function activateTabGroup(group, tabName) { + const tabs = document.querySelectorAll(`.list-output-tab[data-tab-group="${group}"]`); + const buttons = document.querySelectorAll(`.tab-button[data-tab-group="${group}"]`); tabs.forEach(tab => { const isActive = tab.dataset.tab === tabName; tab.classList.toggle("is-active", isActive); @@ -324,19 +654,73 @@ if (typeof document !== "undefined") { document.addEventListener("DOMContentLoaded", function () { if (!document.body.classList.contains("list-output")) return; const data = getListData(); - renderControllerTable(data.controllers); - renderServiceTable(data.services); - renderRepositoryTable(data.repositories); + renderBusinessPackageTable(data.businessRules.packages); + renderBusinessAllTable(data.businessRules.all); + renderBusinessEnumTable(data.businessRules.enums); + renderBusinessCollectionTable(data.businessRules.collections); + renderBusinessValidationTable(data.businessRules.validations); + renderBusinessSmellTable(data.businessRules.methodSmells); + renderControllerTable(data.applications.controllers); + renderServiceTable(data.applications.services); + renderRepositoryTable(data.applications.repositories); const tabButtons = document.querySelectorAll(".list-output-tabs .tab-button"); tabButtons.forEach(button => { - button.addEventListener("click", () => activateTab(button.dataset.tab)); + button.addEventListener("click", () => activateTabGroup(button.dataset.tabGroup, button.dataset.tab)); }); + const businessPackageExportButton = document.getElementById("export-business-package-csv"); + if (businessPackageExportButton) { + businessPackageExportButton.addEventListener("click", () => { + const csvText = buildBusinessPackageCsv(data.businessRules.packages); + downloadCsv(csvText, "list-output-business-package.csv"); + }); + } + + const businessAllExportButton = document.getElementById("export-business-all-csv"); + if (businessAllExportButton) { + businessAllExportButton.addEventListener("click", () => { + const csvText = buildBusinessAllCsv(data.businessRules.all); + downloadCsv(csvText, "list-output-business-all.csv"); + }); + } + + const businessEnumExportButton = document.getElementById("export-business-enum-csv"); + if (businessEnumExportButton) { + businessEnumExportButton.addEventListener("click", () => { + const csvText = buildBusinessEnumCsv(data.businessRules.enums); + downloadCsv(csvText, "list-output-business-enum.csv"); + }); + } + + const businessCollectionExportButton = document.getElementById("export-business-collection-csv"); + if (businessCollectionExportButton) { + businessCollectionExportButton.addEventListener("click", () => { + const csvText = buildBusinessCollectionCsv(data.businessRules.collections); + downloadCsv(csvText, "list-output-business-collection.csv"); + }); + } + + const businessValidationExportButton = document.getElementById("export-business-validation-csv"); + if (businessValidationExportButton) { + businessValidationExportButton.addEventListener("click", () => { + const csvText = buildBusinessValidationCsv(data.businessRules.validations); + downloadCsv(csvText, "list-output-business-validation.csv"); + }); + } + + const businessSmellExportButton = document.getElementById("export-business-smell-csv"); + if (businessSmellExportButton) { + businessSmellExportButton.addEventListener("click", () => { + const csvText = buildBusinessSmellCsv(data.businessRules.methodSmells); + downloadCsv(csvText, "list-output-business-smell.csv"); + }); + } + const controllerExportButton = document.getElementById("export-controller-csv"); if (controllerExportButton) { controllerExportButton.addEventListener("click", () => { - const csvText = buildControllerCsv(data.controllers); + const csvText = buildControllerCsv(data.applications.controllers); downloadCsv(csvText, "list-output-controller.csv"); }); } @@ -344,7 +728,7 @@ if (typeof document !== "undefined") { const serviceExportButton = document.getElementById("export-service-csv"); if (serviceExportButton) { serviceExportButton.addEventListener("click", () => { - const csvText = buildServiceCsv(data.services); + const csvText = buildServiceCsv(data.applications.services); downloadCsv(csvText, "list-output-service.csv"); }); } @@ -352,7 +736,7 @@ if (typeof document !== "undefined") { const repositoryExportButton = document.getElementById("export-repository-csv"); if (repositoryExportButton) { repositoryExportButton.addEventListener("click", () => { - const csvText = buildRepositoryCsv(data.repositories); + const csvText = buildRepositoryCsv(data.applications.repositories); downloadCsv(csvText, "list-output-repository.csv"); }); } @@ -368,8 +752,20 @@ if (typeof module !== "undefined" && module.exports) { buildControllerCsv, buildServiceCsv, buildRepositoryCsv, + buildBusinessPackageCsv, + buildBusinessAllCsv, + buildBusinessEnumCsv, + buildBusinessCollectionCsv, + buildBusinessValidationCsv, + buildBusinessSmellCsv, renderControllerTable, renderServiceTable, renderRepositoryTable, + renderBusinessPackageTable, + renderBusinessAllTable, + renderBusinessEnumTable, + renderBusinessCollectionTable, + renderBusinessValidationTable, + renderBusinessSmellTable, }; } diff --git a/jig-core/src/main/resources/templates/list-output.html b/jig-core/src/main/resources/templates/list-output.html index d516d9293..216592b1f 100644 --- a/jig-core/src/main/resources/templates/list-output.html +++ b/jig-core/src/main/resources/templates/list-output.html @@ -17,142 +17,372 @@

一覧出力

- - - + +
-
-

CONTROLLER

-
- +
+

ビジネスルール一覧

+
+ + + + + +
- - - - - - - - - - - - - - -
パッケージ名クラス名メソッドシグネチャメソッド戻り値の型クラス別名使用しているフィールドの型循環的複雑度パス
-
-
-

SERVICE

-
- -
- - - - - - - - - - - - - - - - - - - - - -
パッケージ名クラス名メソッドシグネチャメソッド戻り値の型イベントハンドラクラス別名メソッド別名メソッド戻り値の型の別名メソッド引数の型の別名使用しているフィールドの型循環的複雑度使用しているサービスのメソッド使用しているリポジトリのメソッドnull使用stream使用
+
+

PACKAGE

+
+ +
+ + + + + + + + + +
パッケージ名パッケージ別名クラス数
+
+ +
+

ALL

+
+ +
+ + + + + + + + + + + + + + + + +
パッケージ名クラス名クラス別名ビジネスルールの種類関連元ビジネスルール数関連先ビジネスルール数関連元クラス数非PUBLIC同パッケージからのみ参照関連元クラス
+
+ +
+

ENUM

+
+ +
+ + + + + + + + + + + + + + + + +
パッケージ名クラス名クラス別名定数宣言フィールド使用箇所数使用箇所パラメーター有り振る舞い有り多態
+
+ +
+

COLLECTION

+
+ +
+ + + + + + + + + + + + + + +
パッケージ名クラス名クラス別名フィールドの型使用箇所数使用箇所メソッド数メソッド一覧
+
+ +
+

VALIDATION

+
+ +
+ + + + + + + + + + + + + +
パッケージ名クラス名クラス別名メンバ名メンバクラス名アノテーションクラス名アノテーション記述
+
+ +
+

注意メソッド

+
+ +
+ + + + + + + + + + + + + + + + + +
パッケージ名クラス名メソッドシグネチャメソッド戻り値の型クラス別名メンバを使用していない基本型の授受を行なっているNULLリテラルを使用しているNULL判定をしている真偽値を返しているvoidを返している
+
-
-

REPOSITORY

-
- +
+

機能一覧

+
+ + +
- - - - - - - - - - - - - - - - - - - - -
パッケージ名クラス名メソッドシグネチャメソッド戻り値の型クラス別名メソッド戻り値の型の別名メソッド引数の型の別名循環的複雑度INSERTSELECTUPDATEDELETE関連元クラス数関連元メソッド数
+ +
+

CONTROLLER

+
+ +
+ + + + + + + + + + + + + + +
パッケージ名クラス名メソッドシグネチャメソッド戻り値の型クラス別名使用しているフィールドの型循環的複雑度パス
+
+ +
+

SERVICE

+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
パッケージ名クラス名メソッドシグネチャメソッド戻り値の型イベントハンドラクラス別名メソッド別名メソッド戻り値の型の別名メソッド引数の型の別名使用しているフィールドの型循環的複雑度使用しているサービスのメソッド使用しているリポジトリのメソッドnull使用stream使用
+
+ +
+

REPOSITORY

+
+ +
+ + + + + + + + + + + + + + + + + + + + +
パッケージ名クラス名メソッドシグネチャメソッド戻り値の型クラス別名メソッド戻り値の型の別名メソッド引数の型の別名循環的複雑度INSERTSELECTUPDATEDELETE関連元クラス数関連元メソッド数
+
diff --git a/jig-core/src/test/js/list-output.test.js b/jig-core/src/test/js/list-output.test.js index b4c43ee0a..bd1c990e6 100644 --- a/jig-core/src/test/js/list-output.test.js +++ b/jig-core/src/test/js/list-output.test.js @@ -141,14 +141,20 @@ test.describe('list-output.js データ読み込み', () => { const doc = setupDocument(); const dataElement = new Element('script'); dataElement.textContent = JSON.stringify({ - controllers: [{typeName: 'ExampleController'}], + applications: { + controllers: [{typeName: 'ExampleController'}], + }, + businessRules: { + packages: [{packageName: 'com.example'}], + }, }); doc.elementsById.set('list-data', dataElement); const data = listOutput.getListData(); - assert.equal(data.controllers.length, 1); - assert.equal(data.controllers[0].typeName, 'ExampleController'); + assert.equal(data.applications.controllers.length, 1); + assert.equal(data.applications.controllers[0].typeName, 'ExampleController'); + assert.equal(data.businessRules.packages.length, 1); }); }); From efee66d0c22d8da017c2a1e944b6495f998b2495 Mon Sep 17 00:00:00 2001 From: irof Date: Tue, 3 Feb 2026 00:57:50 +0900 Subject: [PATCH 4/4] import --- .../model/knowledge/datasource/DatasourceAngle.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngle.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngle.java index 767d83d4b..d463fe4c8 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngle.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngle.java @@ -1,12 +1,14 @@ package org.dddjava.jig.domain.model.knowledge.datasource; import org.dddjava.jig.domain.model.data.rdbaccess.CrudTables; +import org.dddjava.jig.domain.model.data.rdbaccess.Tables; import org.dddjava.jig.domain.model.data.types.JigTypeReference; import org.dddjava.jig.domain.model.data.types.TypeId; import org.dddjava.jig.domain.model.information.members.CallerMethods; import org.dddjava.jig.domain.model.information.members.JigMethod; import org.dddjava.jig.domain.model.information.outputs.OutputImplementation; +import java.util.List; import java.util.stream.Stream; /** @@ -52,7 +54,7 @@ public String insertTables() { return crudTables.create().asText(); } - public java.util.List insertTableNames() { + public List insertTableNames() { return tableNames(crudTables.create()); } @@ -60,7 +62,7 @@ public String selectTables() { return crudTables.read().asText(); } - public java.util.List selectTableNames() { + public List selectTableNames() { return tableNames(crudTables.read()); } @@ -68,7 +70,7 @@ public String updateTables() { return crudTables.update().asText(); } - public java.util.List updateTableNames() { + public List updateTableNames() { return tableNames(crudTables.update()); } @@ -76,7 +78,7 @@ public String deleteTables() { return crudTables.delete().asText(); } - public java.util.List deleteTableNames() { + public List deleteTableNames() { return tableNames(crudTables.delete()); } @@ -100,7 +102,7 @@ public String typeLabel() { return outputImplementation.interfaceJigType().label(); } - private java.util.List tableNames(org.dddjava.jig.domain.model.data.rdbaccess.Tables tables) { + private List tableNames(Tables tables) { return tables.tables().stream() .map(org.dddjava.jig.domain.model.data.rdbaccess.Table::name) .distinct()