重构 MyBatis 生成输出布局与结果反馈
This commit is contained in:
+189
-40
@@ -2,6 +2,11 @@ package com.example.workutils.biz.mybatisgenerator;
|
|||||||
|
|
||||||
import com.example.workutils.biz.mybatisgenerator.application.GenerateArtifactsRequest;
|
import com.example.workutils.biz.mybatisgenerator.application.GenerateArtifactsRequest;
|
||||||
import com.example.workutils.biz.mybatisgenerator.application.GenerateArtifactsUseCase;
|
import com.example.workutils.biz.mybatisgenerator.application.GenerateArtifactsUseCase;
|
||||||
|
import com.example.workutils.biz.mybatisgenerator.application.GenerationPlan;
|
||||||
|
import com.example.workutils.biz.mybatisgenerator.application.GenerationPlanner;
|
||||||
|
import com.example.workutils.biz.mybatisgenerator.application.GenerationResult;
|
||||||
|
import com.example.workutils.biz.mybatisgenerator.application.OutputLayout;
|
||||||
|
import com.example.workutils.biz.mybatisgenerator.application.OutputLayoutPolicy;
|
||||||
import com.example.workutils.biz.mybatisgenerator.application.QueryTablesRequest;
|
import com.example.workutils.biz.mybatisgenerator.application.QueryTablesRequest;
|
||||||
import com.example.workutils.biz.mybatisgenerator.application.QueryTablesUseCase;
|
import com.example.workutils.biz.mybatisgenerator.application.QueryTablesUseCase;
|
||||||
import com.example.workutils.biz.mybatisgenerator.presentation.DatasourceSummary;
|
import com.example.workutils.biz.mybatisgenerator.presentation.DatasourceSummary;
|
||||||
@@ -34,7 +39,6 @@ import javafx.stage.Stage;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@@ -57,8 +61,10 @@ public class MyBatisGeneratorWindow {
|
|||||||
private final WorkUtilsProperties properties;
|
private final WorkUtilsProperties properties;
|
||||||
private final QueryTablesUseCase queryTablesUseCase;
|
private final QueryTablesUseCase queryTablesUseCase;
|
||||||
private final GenerateArtifactsUseCase generateArtifactsUseCase;
|
private final GenerateArtifactsUseCase generateArtifactsUseCase;
|
||||||
|
private final OutputLayoutPolicy outputLayoutPolicy;
|
||||||
|
private final GenerationPlanner generationPlanner;
|
||||||
private final AsyncOperationController<List<String>> queryOperation;
|
private final AsyncOperationController<List<String>> queryOperation;
|
||||||
private final AsyncOperationController<String> generateOperation;
|
private final AsyncOperationController<GenerationResult> generateOperation;
|
||||||
|
|
||||||
// ---- UI 组件 ----
|
// ---- UI 组件 ----
|
||||||
private TextField patternField;
|
private TextField patternField;
|
||||||
@@ -67,6 +73,8 @@ public class MyBatisGeneratorWindow {
|
|||||||
private TextField modelDirField;
|
private TextField modelDirField;
|
||||||
private TextField xmlDirField;
|
private TextField xmlDirField;
|
||||||
private TextField mapperDirField;
|
private TextField mapperDirField;
|
||||||
|
private TextField projectRootField;
|
||||||
|
private CheckBox customOutputCb;
|
||||||
private TreeView<String> projectTree;
|
private TreeView<String> projectTree;
|
||||||
private ComboBox<String> datasourceCmb;
|
private ComboBox<String> datasourceCmb;
|
||||||
private Label dbUrlLabel;
|
private Label dbUrlLabel;
|
||||||
@@ -115,9 +123,11 @@ public class MyBatisGeneratorWindow {
|
|||||||
this.generateArtifactsUseCase = new GenerateArtifactsUseCase(
|
this.generateArtifactsUseCase = new GenerateArtifactsUseCase(
|
||||||
request -> callback.generate(
|
request -> callback.generate(
|
||||||
request.tables(),
|
request.tables(),
|
||||||
request.modelDirectory(),
|
request.outputLayout().modelDirectory().toString(),
|
||||||
request.xmlDirectory(),
|
request.outputLayout().xmlDirectory().toString(),
|
||||||
request.mapperDirectory()));
|
request.outputLayout().mapperDirectory().toString()));
|
||||||
|
this.outputLayoutPolicy = new OutputLayoutPolicy();
|
||||||
|
this.generationPlanner = new GenerationPlanner();
|
||||||
this.queryOperation = new AsyncOperationController<>(
|
this.queryOperation = new AsyncOperationController<>(
|
||||||
AppExecutors.io(), Platform::runLater);
|
AppExecutors.io(), Platform::runLater);
|
||||||
this.generateOperation = new AsyncOperationController<>(
|
this.generateOperation = new AsyncOperationController<>(
|
||||||
@@ -429,7 +439,32 @@ public class MyBatisGeneratorWindow {
|
|||||||
Label dirLabel = new Label("输出目录设置:");
|
Label dirLabel = new Label("输出目录设置:");
|
||||||
dirLabel.setFont(Theme.heading(fsSection()));
|
dirLabel.setFont(Theme.heading(fsSection()));
|
||||||
dirLabel.setTextFill(Theme.TEXT_PRIMARY);
|
dirLabel.setTextFill(Theme.TEXT_PRIMARY);
|
||||||
box.getChildren().add(dirLabel);
|
projectRootField = new TextField();
|
||||||
|
projectRootField.getStyleClass().add(Theme.CSS_TEXT_FIELD);
|
||||||
|
HBox.setHgrow(projectRootField, Priority.ALWAYS);
|
||||||
|
projectRootField.setOnAction(e -> refreshDerivedDirectories());
|
||||||
|
projectRootField.focusedProperty().addListener((obs, oldValue, focused) -> {
|
||||||
|
if (!focused) refreshDerivedDirectories();
|
||||||
|
});
|
||||||
|
|
||||||
|
Button browseRootBtn = StyledButton.small(
|
||||||
|
"浏览...", Font.font("Microsoft YaHei", fsBody() - 1));
|
||||||
|
browseRootBtn.setOnAction(e -> onBrowseProjectRoot());
|
||||||
|
Button useTreeBtn = StyledButton.small(
|
||||||
|
"使用选中项目", Font.font("Microsoft YaHei", fsBody() - 1));
|
||||||
|
useTreeBtn.setOnAction(e -> useSelectedProjectRoot());
|
||||||
|
Label rootLabel = new Label("项目根目录:");
|
||||||
|
rootLabel.setPrefWidth(110);
|
||||||
|
HBox rootRow = new HBox(
|
||||||
|
Theme.S2, rootLabel, projectRootField, browseRootBtn, useTreeBtn);
|
||||||
|
rootRow.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
|
||||||
|
customOutputCb = new CheckBox("自定义输出目录(高级)");
|
||||||
|
customOutputCb.setSelected(false);
|
||||||
|
customOutputCb.selectedProperty().addListener((obs, oldValue, custom) -> {
|
||||||
|
setDirectoryFieldsEditable(custom);
|
||||||
|
if (!custom) refreshDerivedDirectories();
|
||||||
|
});
|
||||||
|
|
||||||
HBox modelRow = createDirRow("Model (PO)", fm -> modelDirField = fm);
|
HBox modelRow = createDirRow("Model (PO)", fm -> modelDirField = fm);
|
||||||
modelDirField = (TextField) modelRow.getChildren().get(1);
|
modelDirField = (TextField) modelRow.getChildren().get(1);
|
||||||
@@ -438,9 +473,12 @@ public class MyBatisGeneratorWindow {
|
|||||||
HBox mapperRow = createDirRow("Mapper 接口", fm -> mapperDirField = fm);
|
HBox mapperRow = createDirRow("Mapper 接口", fm -> mapperDirField = fm);
|
||||||
mapperDirField = (TextField) mapperRow.getChildren().get(1);
|
mapperDirField = (TextField) mapperRow.getChildren().get(1);
|
||||||
|
|
||||||
loadDefaultDirs();
|
initializeProjectRoot();
|
||||||
|
setDirectoryFieldsEditable(false);
|
||||||
|
|
||||||
box.getChildren().addAll(modelRow, xmlRow, mapperRow);
|
box.getChildren().addAll(
|
||||||
|
dirLabel, rootRow, customOutputCb,
|
||||||
|
modelRow, xmlRow, mapperRow);
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,6 +496,7 @@ public class MyBatisGeneratorWindow {
|
|||||||
|
|
||||||
Button browseBtn = StyledButton.small("浏览...", Font.font("Microsoft YaHei", fsBody() - 1));
|
Button browseBtn = StyledButton.small("浏览...", Font.font("Microsoft YaHei", fsBody() - 1));
|
||||||
browseBtn.setOnAction(e -> onBrowseDir(field));
|
browseBtn.setOnAction(e -> onBrowseDir(field));
|
||||||
|
browseBtn.disableProperty().bind(field.editableProperty().not());
|
||||||
|
|
||||||
Button fillBtn = StyledButton.small("填充", Font.font("Microsoft YaHei", fsBody() - 1));
|
Button fillBtn = StyledButton.small("填充", Font.font("Microsoft YaHei", fsBody() - 1));
|
||||||
fillBtn.setOnAction(e -> {
|
fillBtn.setOnAction(e -> {
|
||||||
@@ -469,44 +508,85 @@ public class MyBatisGeneratorWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
fillBtn.disableProperty().bind(field.editableProperty().not());
|
||||||
|
|
||||||
if (fieldRef != null) fieldRef.accept(field);
|
if (fieldRef != null) fieldRef.accept(field);
|
||||||
row.getChildren().addAll(lbl, field, browseBtn, fillBtn);
|
row.getChildren().addAll(lbl, field, browseBtn, fillBtn);
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDefaultDirs() {
|
private void initializeProjectRoot() {
|
||||||
Properties p = new Properties();
|
Path root = Paths.get("").toAbsolutePath().normalize();
|
||||||
// 1. classpath 内置默认值
|
try {
|
||||||
try (InputStream is = getClass().getClassLoader().getResourceAsStream("mybatis-generator.properties")) {
|
List<String> paths = fileOperationHelper.getProjectPaths();
|
||||||
if (is != null) p.load(is);
|
if (!paths.isEmpty() && Files.isDirectory(Paths.get(paths.get(0)))) {
|
||||||
} catch (Exception ignored) {}
|
root = Paths.get(paths.get(0)).toAbsolutePath().normalize();
|
||||||
// 2. data/ 外部配置覆盖
|
}
|
||||||
Path dataFile = properties.getMybatisGeneratorFilePath();
|
} catch (Exception ignored) {
|
||||||
if (java.nio.file.Files.exists(dataFile)) {
|
}
|
||||||
try (InputStream is = new java.io.FileInputStream(dataFile.toFile())) {
|
projectRootField.setText(root.toString().replace('\\', '/'));
|
||||||
Properties externalProps = new Properties();
|
refreshDerivedDirectories();
|
||||||
externalProps.load(is);
|
|
||||||
p.putAll(externalProps);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setDirField(modelDirField, p.getProperty("model_target_project", "./generator"));
|
private void setDirectoryFieldsEditable(boolean editable) {
|
||||||
setDirField(xmlDirField, p.getProperty("sqlmapper_target_project", "./generator"));
|
modelDirField.setEditable(editable);
|
||||||
setDirField(mapperDirField, p.getProperty("dao_target_project", "./generator"));
|
xmlDirField.setEditable(editable);
|
||||||
|
mapperDirField.setEditable(editable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDirField(TextField field, String dir) {
|
private void refreshDerivedDirectories() {
|
||||||
if (field == null || dir == null) return;
|
if (customOutputCb != null && customOutputCb.isSelected()) return;
|
||||||
if (dir.startsWith("./") || dir.startsWith(".\\")) {
|
try {
|
||||||
field.setText(new File(dir).getAbsolutePath().replace('\\', '/'));
|
OutputLayout layout = outputLayoutPolicy.derive(
|
||||||
} else {
|
Paths.get(projectRootField.getText().trim()));
|
||||||
field.setText(dir.replace('\\', '/'));
|
setDirectoryFields(layout);
|
||||||
|
} catch (IllegalArgumentException error) {
|
||||||
|
modelDirField.clear();
|
||||||
|
xmlDirField.clear();
|
||||||
|
mapperDirField.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setDirectoryFields(OutputLayout layout) {
|
||||||
|
modelDirField.setText(pathText(layout.modelDirectory()));
|
||||||
|
xmlDirField.setText(pathText(layout.xmlDirectory()));
|
||||||
|
mapperDirField.setText(pathText(layout.mapperDirectory()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String pathText(Path path) {
|
||||||
|
return path.toString().replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 事件 ====================
|
// ==================== 事件 ====================
|
||||||
|
|
||||||
|
private void onBrowseProjectRoot() {
|
||||||
|
DirectoryChooser chooser = new DirectoryChooser();
|
||||||
|
chooser.setTitle("选择项目根目录");
|
||||||
|
Path current = parseProjectRoot();
|
||||||
|
if (current != null && Files.isDirectory(current)) {
|
||||||
|
chooser.setInitialDirectory(current.toFile());
|
||||||
|
}
|
||||||
|
File selected = chooser.showDialog(stage);
|
||||||
|
if (selected != null) {
|
||||||
|
projectRootField.setText(pathText(selected.toPath()));
|
||||||
|
refreshDerivedDirectories();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useSelectedProjectRoot() {
|
||||||
|
TreeItem<String> selected = projectTree.getSelectionModel().getSelectedItem();
|
||||||
|
String path = buildTreePath(selected);
|
||||||
|
if (path != null && Files.isDirectory(Paths.get(path))) {
|
||||||
|
projectRootField.setText(pathText(Paths.get(path)));
|
||||||
|
refreshDerivedDirectories();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path parseProjectRoot() {
|
||||||
|
String text = projectRootField == null ? "" : projectRootField.getText().trim();
|
||||||
|
return text.isEmpty() ? null : Paths.get(text).toAbsolutePath().normalize();
|
||||||
|
}
|
||||||
|
|
||||||
private void onBrowseDir(TextField targetField) {
|
private void onBrowseDir(TextField targetField) {
|
||||||
DirectoryChooser chooser = new DirectoryChooser();
|
DirectoryChooser chooser = new DirectoryChooser();
|
||||||
chooser.setTitle("选择目录");
|
chooser.setTitle("选择目录");
|
||||||
@@ -565,28 +645,97 @@ public class MyBatisGeneratorWindow {
|
|||||||
|
|
||||||
private void onGenerate() {
|
private void onGenerate() {
|
||||||
try {
|
try {
|
||||||
GenerateArtifactsRequest request = new GenerateArtifactsRequest(
|
List<String> selectedTables = tableItems.stream()
|
||||||
tableItems.stream()
|
|
||||||
.filter(TableItem::isSelected)
|
.filter(TableItem::isSelected)
|
||||||
.map(TableItem::getTableName)
|
.map(TableItem::getTableName)
|
||||||
.collect(Collectors.toList()),
|
.collect(Collectors.toList());
|
||||||
getDirText(modelDirField),
|
OutputLayout layout = buildOutputLayout();
|
||||||
getDirText(xmlDirField),
|
GenerateArtifactsRequest request = new GenerateArtifactsRequest(
|
||||||
getDirText(mapperDirField));
|
selectedTables, layout);
|
||||||
|
GenerationPlan plan = generationPlanner.plan(selectedTables, layout);
|
||||||
|
if (!confirmGeneration(plan)) return;
|
||||||
generateOperation.start(
|
generateOperation.start(
|
||||||
"正在生成代码",
|
"正在生成代码",
|
||||||
context -> generateArtifactsUseCase.execute(request),
|
context -> generateArtifactsUseCase.execute(request),
|
||||||
result -> showInfo("生成结果", result),
|
result -> showInfo("生成结果", formatGenerationResult(result)),
|
||||||
error -> showAlert("生成失败: " + error.getMessage()));
|
error -> showAlert("生成失败: " + error.getMessage()));
|
||||||
} catch (IllegalArgumentException error) {
|
} catch (RuntimeException error) {
|
||||||
showAlert(error.getMessage());
|
showAlert(error.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OutputLayout buildOutputLayout() {
|
||||||
|
Path root = parseProjectRoot();
|
||||||
|
if (root == null) {
|
||||||
|
throw new IllegalArgumentException("请选择项目根目录");
|
||||||
|
}
|
||||||
|
if (!customOutputCb.isSelected()) {
|
||||||
|
return outputLayoutPolicy.derive(root);
|
||||||
|
}
|
||||||
|
return outputLayoutPolicy.custom(
|
||||||
|
root,
|
||||||
|
Paths.get(getDirText(modelDirField)),
|
||||||
|
Paths.get(getDirText(xmlDirField)),
|
||||||
|
Paths.get(getDirText(mapperDirField)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean confirmGeneration(GenerationPlan plan) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
|
alert.setTitle("确认生成");
|
||||||
|
alert.setHeaderText("即将为 " + plan.tables().size() + " 个表生成代码");
|
||||||
|
alert.setContentText(buildGenerationSummary(plan));
|
||||||
|
alert.initOwner(stage);
|
||||||
|
return alert.showAndWait().filter(ButtonType.OK::equals).isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildGenerationSummary(GenerationPlan plan) {
|
||||||
|
return "表: " + String.join(", ", plan.tables())
|
||||||
|
+ "\n\nModel: " + formatRisk(plan.modelRisk())
|
||||||
|
+ "\nXML Mapper: " + formatRisk(plan.xmlRisk())
|
||||||
|
+ "\nMapper 接口: " + formatRisk(plan.mapperRisk())
|
||||||
|
+ "\n\n已有文件可能被生成器覆盖,请确认后继续。";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatRisk(
|
||||||
|
com.example.workutils.biz.mybatisgenerator.application.DirectoryRisk risk) {
|
||||||
|
String state = risk.willCreate()
|
||||||
|
? "待创建"
|
||||||
|
: "已有 " + risk.existingFileCount() + " 个相关文件";
|
||||||
|
return pathText(risk.directory()) + "(" + state + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatGenerationResult(GenerationResult result) {
|
||||||
|
StringBuilder text = new StringBuilder();
|
||||||
|
text.append("新增文件: ").append(result.createdFiles().size()).append('\n');
|
||||||
|
appendFiles(text, result.createdFiles());
|
||||||
|
text.append("\n更新文件: ").append(result.updatedFiles().size()).append('\n');
|
||||||
|
appendFiles(text, result.updatedFiles());
|
||||||
|
text.append("\n未变化文件: ").append(result.unchangedFiles().size()).append('\n');
|
||||||
|
if (!result.warnings().isEmpty()) {
|
||||||
|
text.append("\n警告:\n");
|
||||||
|
result.warnings().forEach(warning -> text.append("- ").append(warning).append('\n'));
|
||||||
|
}
|
||||||
|
if (!result.generatorMessage().isBlank()) {
|
||||||
|
text.append("\n生成器消息:\n").append(result.generatorMessage());
|
||||||
|
}
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendFiles(StringBuilder text, List<Path> files) {
|
||||||
|
int visibleCount = Math.min(files.size(), 20);
|
||||||
|
for (int index = 0; index < visibleCount; index++) {
|
||||||
|
text.append("- ").append(pathText(files.get(index))).append('\n');
|
||||||
|
}
|
||||||
|
if (files.size() > visibleCount) {
|
||||||
|
text.append("... 另有 ").append(files.size() - visibleCount).append(" 个文件\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void resetForm() {
|
private void resetForm() {
|
||||||
patternField.clear();
|
patternField.clear();
|
||||||
tableItems.clear();
|
tableItems.clear();
|
||||||
loadDefaultDirs();
|
customOutputCb.setSelected(false);
|
||||||
|
initializeProjectRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDirText(TextField field) {
|
private String getDirText(TextField field) {
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record DirectoryRisk(
|
||||||
|
Path directory,
|
||||||
|
boolean exists,
|
||||||
|
boolean writable,
|
||||||
|
boolean willCreate,
|
||||||
|
long existingFileCount) {
|
||||||
|
|
||||||
|
public DirectoryRisk {
|
||||||
|
directory = Objects.requireNonNull(directory, "directory")
|
||||||
|
.toAbsolutePath()
|
||||||
|
.normalize();
|
||||||
|
if (existingFileCount < 0) {
|
||||||
|
throw new IllegalArgumentException("existingFileCount 不能为负数");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-12
@@ -5,23 +5,13 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public record GenerateArtifactsRequest(
|
public record GenerateArtifactsRequest(
|
||||||
List<String> tables,
|
List<String> tables,
|
||||||
String modelDirectory,
|
OutputLayout outputLayout) {
|
||||||
String xmlDirectory,
|
|
||||||
String mapperDirectory) {
|
|
||||||
|
|
||||||
public GenerateArtifactsRequest {
|
public GenerateArtifactsRequest {
|
||||||
tables = List.copyOf(Objects.requireNonNull(tables, "tables"));
|
tables = List.copyOf(Objects.requireNonNull(tables, "tables"));
|
||||||
if (tables.isEmpty()) {
|
if (tables.isEmpty()) {
|
||||||
throw new IllegalArgumentException("请至少选择一个表");
|
throw new IllegalArgumentException("请至少选择一个表");
|
||||||
}
|
}
|
||||||
modelDirectory = directoryOrDefault(modelDirectory);
|
outputLayout = Objects.requireNonNull(outputLayout, "outputLayout");
|
||||||
xmlDirectory = directoryOrDefault(xmlDirectory);
|
|
||||||
mapperDirectory = directoryOrDefault(mapperDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String directoryOrDefault(String value) {
|
|
||||||
return value == null || value.isBlank()
|
|
||||||
? "./generator"
|
|
||||||
: value.trim();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-3
@@ -10,12 +10,26 @@ public final class GenerateArtifactsUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final Gateway gateway;
|
private final Gateway gateway;
|
||||||
|
private final GenerationPlanner planner;
|
||||||
|
|
||||||
public GenerateArtifactsUseCase(Gateway gateway) {
|
public GenerateArtifactsUseCase(Gateway gateway) {
|
||||||
this.gateway = Objects.requireNonNull(gateway, "gateway");
|
this(gateway, new GenerationPlanner());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String execute(GenerateArtifactsRequest request) throws Exception {
|
GenerateArtifactsUseCase(
|
||||||
return gateway.generate(Objects.requireNonNull(request, "request"));
|
Gateway gateway,
|
||||||
|
GenerationPlanner planner) {
|
||||||
|
this.gateway = Objects.requireNonNull(gateway, "gateway");
|
||||||
|
this.planner = Objects.requireNonNull(planner, "planner");
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenerationResult execute(GenerateArtifactsRequest request) throws Exception {
|
||||||
|
GenerateArtifactsRequest safeRequest =
|
||||||
|
Objects.requireNonNull(request, "request");
|
||||||
|
planner.plan(safeRequest.tables(), safeRequest.outputLayout());
|
||||||
|
OutputSnapshot before = OutputSnapshot.capture(safeRequest.outputLayout());
|
||||||
|
String message = gateway.generate(safeRequest);
|
||||||
|
OutputSnapshot after = OutputSnapshot.capture(safeRequest.outputLayout());
|
||||||
|
return before.compare(after, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record GenerationPlan(
|
||||||
|
List<String> tables,
|
||||||
|
DirectoryRisk modelRisk,
|
||||||
|
DirectoryRisk xmlRisk,
|
||||||
|
DirectoryRisk mapperRisk) {
|
||||||
|
|
||||||
|
public GenerationPlan {
|
||||||
|
tables = List.copyOf(Objects.requireNonNull(tables, "tables"));
|
||||||
|
modelRisk = Objects.requireNonNull(modelRisk, "modelRisk");
|
||||||
|
xmlRisk = Objects.requireNonNull(xmlRisk, "xmlRisk");
|
||||||
|
mapperRisk = Objects.requireNonNull(mapperRisk, "mapperRisk");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateWritableDirectories() {
|
||||||
|
validate(modelRisk);
|
||||||
|
validate(xmlRisk);
|
||||||
|
validate(mapperRisk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validate(DirectoryRisk risk) {
|
||||||
|
if (risk.exists() && !risk.writable()) {
|
||||||
|
throw new IllegalArgumentException("输出目录不可写: " + risk.directory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public final class GenerationPlanner {
|
||||||
|
|
||||||
|
public GenerationPlan plan(List<String> tables, OutputLayout layout) {
|
||||||
|
Objects.requireNonNull(layout, "layout");
|
||||||
|
GenerationPlan plan = new GenerationPlan(
|
||||||
|
List.copyOf(Objects.requireNonNull(tables, "tables")),
|
||||||
|
inspect(layout.modelDirectory(), ".java"),
|
||||||
|
inspect(layout.xmlDirectory(), ".xml"),
|
||||||
|
inspect(layout.mapperDirectory(), ".java"));
|
||||||
|
plan.validateWritableDirectories();
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DirectoryRisk inspect(Path directory, String extension) {
|
||||||
|
boolean exists = Files.isDirectory(directory);
|
||||||
|
return new DirectoryRisk(
|
||||||
|
directory,
|
||||||
|
exists,
|
||||||
|
!exists || Files.isWritable(directory),
|
||||||
|
!exists,
|
||||||
|
exists ? countFiles(directory, extension) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long countFiles(Path directory, String extension) {
|
||||||
|
try (Stream<Path> paths = Files.walk(directory)) {
|
||||||
|
String suffix = extension.toLowerCase(Locale.ROOT);
|
||||||
|
return paths.filter(Files::isRegularFile)
|
||||||
|
.filter(path -> path.getFileName().toString()
|
||||||
|
.toLowerCase(Locale.ROOT).endsWith(suffix))
|
||||||
|
.count();
|
||||||
|
} catch (IOException error) {
|
||||||
|
throw new UncheckedIOException(
|
||||||
|
"无法检查输出目录: " + directory, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record GenerationResult(
|
||||||
|
List<Path> createdFiles,
|
||||||
|
List<Path> updatedFiles,
|
||||||
|
List<Path> unchangedFiles,
|
||||||
|
List<String> warnings,
|
||||||
|
String generatorMessage) {
|
||||||
|
|
||||||
|
public GenerationResult {
|
||||||
|
createdFiles = List.copyOf(Objects.requireNonNull(createdFiles, "createdFiles"));
|
||||||
|
updatedFiles = List.copyOf(Objects.requireNonNull(updatedFiles, "updatedFiles"));
|
||||||
|
unchangedFiles = List.copyOf(Objects.requireNonNull(unchangedFiles, "unchangedFiles"));
|
||||||
|
warnings = List.copyOf(Objects.requireNonNull(warnings, "warnings"));
|
||||||
|
generatorMessage = generatorMessage == null ? "" : generatorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record OutputLayout(
|
||||||
|
Path projectRoot,
|
||||||
|
Path modelDirectory,
|
||||||
|
Path xmlDirectory,
|
||||||
|
Path mapperDirectory) {
|
||||||
|
|
||||||
|
public OutputLayout {
|
||||||
|
projectRoot = normalize(projectRoot, "projectRoot");
|
||||||
|
modelDirectory = normalize(modelDirectory, "modelDirectory");
|
||||||
|
xmlDirectory = normalize(xmlDirectory, "xmlDirectory");
|
||||||
|
mapperDirectory = normalize(mapperDirectory, "mapperDirectory");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path normalize(Path path, String name) {
|
||||||
|
return Objects.requireNonNull(path, name)
|
||||||
|
.toAbsolutePath()
|
||||||
|
.normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class OutputLayoutPolicy {
|
||||||
|
|
||||||
|
public OutputLayout derive(Path projectRoot) {
|
||||||
|
Path root = requireProjectRoot(projectRoot);
|
||||||
|
return new OutputLayout(
|
||||||
|
root,
|
||||||
|
root.resolve("src/main/java"),
|
||||||
|
root.resolve("src/main/resources"),
|
||||||
|
root.resolve("src/main/java"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputLayout custom(
|
||||||
|
Path projectRoot,
|
||||||
|
Path modelDirectory,
|
||||||
|
Path xmlDirectory,
|
||||||
|
Path mapperDirectory) {
|
||||||
|
return new OutputLayout(
|
||||||
|
requireProjectRoot(projectRoot),
|
||||||
|
Objects.requireNonNull(modelDirectory, "modelDirectory"),
|
||||||
|
Objects.requireNonNull(xmlDirectory, "xmlDirectory"),
|
||||||
|
Objects.requireNonNull(mapperDirectory, "mapperDirectory"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path requireProjectRoot(Path projectRoot) {
|
||||||
|
Path root = Objects.requireNonNull(projectRoot, "projectRoot")
|
||||||
|
.toAbsolutePath()
|
||||||
|
.normalize();
|
||||||
|
if (!Files.isDirectory(root)) {
|
||||||
|
throw new IllegalArgumentException("项目根目录不存在或不是目录: " + root);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public final class OutputSnapshot {
|
||||||
|
|
||||||
|
private final Map<Path, FileFingerprint> files;
|
||||||
|
private final List<String> warnings;
|
||||||
|
|
||||||
|
private OutputSnapshot(
|
||||||
|
Map<Path, FileFingerprint> files,
|
||||||
|
List<String> warnings) {
|
||||||
|
this.files = Map.copyOf(files);
|
||||||
|
this.warnings = List.copyOf(warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OutputSnapshot capture(OutputLayout layout) {
|
||||||
|
Map<Path, FileFingerprint> files = new LinkedHashMap<>();
|
||||||
|
List<String> warnings = new ArrayList<>();
|
||||||
|
Set<Path> roots = new LinkedHashSet<>(List.of(
|
||||||
|
layout.modelDirectory(),
|
||||||
|
layout.xmlDirectory(),
|
||||||
|
layout.mapperDirectory()));
|
||||||
|
for (Path root : roots) {
|
||||||
|
captureRoot(root, files, warnings);
|
||||||
|
}
|
||||||
|
return new OutputSnapshot(files, warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenerationResult compare(
|
||||||
|
OutputSnapshot after,
|
||||||
|
String generatorMessage) {
|
||||||
|
List<Path> created = new ArrayList<>();
|
||||||
|
List<Path> updated = new ArrayList<>();
|
||||||
|
List<Path> unchanged = new ArrayList<>();
|
||||||
|
for (Map.Entry<Path, FileFingerprint> entry : after.files.entrySet()) {
|
||||||
|
FileFingerprint previous = files.get(entry.getKey());
|
||||||
|
if (previous == null) {
|
||||||
|
created.add(entry.getKey());
|
||||||
|
} else if (!previous.equals(entry.getValue())) {
|
||||||
|
updated.add(entry.getKey());
|
||||||
|
} else {
|
||||||
|
unchanged.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
created.sort(Path::compareTo);
|
||||||
|
updated.sort(Path::compareTo);
|
||||||
|
unchanged.sort(Path::compareTo);
|
||||||
|
List<String> combinedWarnings = new ArrayList<>(warnings);
|
||||||
|
combinedWarnings.addAll(after.warnings);
|
||||||
|
return new GenerationResult(
|
||||||
|
created, updated, unchanged,
|
||||||
|
combinedWarnings, generatorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void captureRoot(
|
||||||
|
Path root,
|
||||||
|
Map<Path, FileFingerprint> files,
|
||||||
|
List<String> warnings) {
|
||||||
|
if (!Files.isDirectory(root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (Stream<Path> paths = Files.walk(root)) {
|
||||||
|
paths.filter(Files::isRegularFile)
|
||||||
|
.filter(OutputSnapshot::isGeneratedSource)
|
||||||
|
.forEach(path -> captureFile(path, files, warnings));
|
||||||
|
} catch (IOException error) {
|
||||||
|
warnings.add("无法扫描输出目录 " + root + ": " + error.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void captureFile(
|
||||||
|
Path path,
|
||||||
|
Map<Path, FileFingerprint> files,
|
||||||
|
List<String> warnings) {
|
||||||
|
Path normalized = path.toAbsolutePath().normalize();
|
||||||
|
try {
|
||||||
|
files.put(normalized, new FileFingerprint(
|
||||||
|
Files.size(normalized),
|
||||||
|
Files.getLastModifiedTime(normalized).toMillis()));
|
||||||
|
} catch (IOException error) {
|
||||||
|
warnings.add("无法读取输出文件 " + normalized + ": " + error.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isGeneratedSource(Path path) {
|
||||||
|
String name = path.getFileName().toString().toLowerCase(Locale.ROOT);
|
||||||
|
return name.endsWith(".java") || name.endsWith(".xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
private record FileFingerprint(long size, long modifiedMillis) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class MyBatisGeneratorWindowArchitectureTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void usesProjectRootPolicyAndAdvancedCustomDirectoryMode() throws IOException {
|
||||||
|
String source = Files.readString(Path.of(
|
||||||
|
"src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java"));
|
||||||
|
|
||||||
|
assertTrue(source.contains("projectRootField"));
|
||||||
|
assertTrue(source.contains("customOutputCb"));
|
||||||
|
assertTrue(source.contains("OutputLayoutPolicy"));
|
||||||
|
assertTrue(source.contains("setDirectoryFieldsEditable"));
|
||||||
|
assertTrue(source.contains("buildGenerationSummary"));
|
||||||
|
assertTrue(source.contains("formatGenerationResult"));
|
||||||
|
assertFalse(source.contains("Files.walk"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class GenerationPlannerTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reportsExistingFilesAndDirectoriesWaitingToBeCreated() throws IOException {
|
||||||
|
Path javaRoot = Files.createDirectories(tempDir.resolve("src/main/java"));
|
||||||
|
Files.writeString(javaRoot.resolve("Account.java"), "class Account {}");
|
||||||
|
Files.writeString(javaRoot.resolve("README.txt"), "ignored");
|
||||||
|
OutputLayout layout = new OutputLayout(
|
||||||
|
tempDir,
|
||||||
|
javaRoot,
|
||||||
|
tempDir.resolve("src/main/resources"),
|
||||||
|
javaRoot);
|
||||||
|
|
||||||
|
GenerationPlan plan = new GenerationPlanner().plan(
|
||||||
|
List.of("account", "trade"), layout);
|
||||||
|
|
||||||
|
assertEquals(List.of("account", "trade"), plan.tables());
|
||||||
|
assertEquals(1, plan.modelRisk().existingFileCount());
|
||||||
|
assertEquals(1, plan.mapperRisk().existingFileCount());
|
||||||
|
assertFalse(plan.xmlRisk().exists());
|
||||||
|
assertTrue(plan.xmlRisk().willCreate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsAPlanContainingAnUnwritableExistingDirectory() {
|
||||||
|
DirectoryRisk blocked = new DirectoryRisk(
|
||||||
|
tempDir.resolve("blocked"), true, false, false, 0);
|
||||||
|
GenerationPlan plan = new GenerationPlan(
|
||||||
|
List.of("account"), blocked, blocked, blocked);
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
plan::validateWritableDirectories);
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-6
@@ -1,7 +1,9 @@
|
|||||||
package com.example.workutils.biz.mybatisgenerator.application;
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -10,6 +12,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||||||
|
|
||||||
class GeneratorRequestTest {
|
class GeneratorRequestTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void queryRequiresAndTrimsPattern() {
|
void queryRequiresAndTrimsPattern() {
|
||||||
assertEquals("trade%",
|
assertEquals("trade%",
|
||||||
@@ -19,23 +24,22 @@ class GeneratorRequestTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void generateCopiesTablesAndDefaultsDirectories() {
|
void generateCopiesTablesAndKeepsOutputLayout() {
|
||||||
List<String> tables = new ArrayList<>(List.of("trade_order"));
|
List<String> tables = new ArrayList<>(List.of("trade_order"));
|
||||||
|
OutputLayout layout = new OutputLayoutPolicy().derive(tempDir);
|
||||||
GenerateArtifactsRequest request = new GenerateArtifactsRequest(
|
GenerateArtifactsRequest request = new GenerateArtifactsRequest(
|
||||||
tables, " ", null, "./custom");
|
tables, layout);
|
||||||
|
|
||||||
tables.add("trade_item");
|
tables.add("trade_item");
|
||||||
|
|
||||||
assertEquals(List.of("trade_order"), request.tables());
|
assertEquals(List.of("trade_order"), request.tables());
|
||||||
assertEquals("./generator", request.modelDirectory());
|
assertEquals(layout, request.outputLayout());
|
||||||
assertEquals("./generator", request.xmlDirectory());
|
|
||||||
assertEquals("./custom", request.mapperDirectory());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void generateRequiresSelectedTable() {
|
void generateRequiresSelectedTable() {
|
||||||
assertThrows(IllegalArgumentException.class,
|
assertThrows(IllegalArgumentException.class,
|
||||||
() -> new GenerateArtifactsRequest(
|
() -> new GenerateArtifactsRequest(
|
||||||
List.of(), "a", "b", "c"));
|
List.of(), new OutputLayoutPolicy().derive(tempDir)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-4
@@ -1,7 +1,10 @@
|
|||||||
package com.example.workutils.biz.mybatisgenerator.application;
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
@@ -10,6 +13,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
|
|
||||||
class GeneratorUseCaseTest {
|
class GeneratorUseCaseTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void queryDelegatesAndCopiesResult() throws Exception {
|
void queryDelegatesAndCopiesResult() throws Exception {
|
||||||
List<String> gatewayResult =
|
List<String> gatewayResult =
|
||||||
@@ -25,20 +31,27 @@ class GeneratorUseCaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void generateDelegatesCompleteRequest() throws Exception {
|
void generateDelegatesRequestAndReturnsFileDifference() throws Exception {
|
||||||
AtomicReference<GenerateArtifactsRequest> received =
|
AtomicReference<GenerateArtifactsRequest> received =
|
||||||
new AtomicReference<>();
|
new AtomicReference<>();
|
||||||
|
OutputLayout layout = new OutputLayoutPolicy().derive(tempDir);
|
||||||
GenerateArtifactsUseCase useCase =
|
GenerateArtifactsUseCase useCase =
|
||||||
new GenerateArtifactsUseCase(request -> {
|
new GenerateArtifactsUseCase(request -> {
|
||||||
received.set(request);
|
received.set(request);
|
||||||
|
Files.createDirectories(request.outputLayout().modelDirectory());
|
||||||
|
Files.writeString(
|
||||||
|
request.outputLayout().modelDirectory().resolve("TradeOrder.java"),
|
||||||
|
"class TradeOrder {}");
|
||||||
return "generated";
|
return "generated";
|
||||||
});
|
});
|
||||||
GenerateArtifactsRequest request = new GenerateArtifactsRequest(
|
GenerateArtifactsRequest request = new GenerateArtifactsRequest(
|
||||||
List.of("trade_order"), "model", "xml", "mapper");
|
List.of("trade_order"), layout);
|
||||||
|
|
||||||
String result = useCase.execute(request);
|
GenerationResult result = useCase.execute(request);
|
||||||
|
|
||||||
assertEquals("generated", result);
|
assertEquals("generated", result.generatorMessage());
|
||||||
|
assertEquals(List.of(layout.modelDirectory().resolve("TradeOrder.java")),
|
||||||
|
result.createdFiles());
|
||||||
assertEquals(request, received.get());
|
assertEquals(request, received.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
class OutputLayoutPolicyTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void derivesStandardDirectoriesFromProjectRoot() {
|
||||||
|
OutputLayout layout = new OutputLayoutPolicy().derive(tempDir);
|
||||||
|
|
||||||
|
Path root = tempDir.toAbsolutePath().normalize();
|
||||||
|
assertEquals(root, layout.projectRoot());
|
||||||
|
assertEquals(root.resolve("src/main/java"), layout.modelDirectory());
|
||||||
|
assertEquals(root.resolve("src/main/resources"), layout.xmlDirectory());
|
||||||
|
assertEquals(root.resolve("src/main/java"), layout.mapperDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsARegularFileAsProjectRoot() throws IOException {
|
||||||
|
Path file = Files.createFile(tempDir.resolve("pom.xml"));
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new OutputLayoutPolicy().derive(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void keepsExplicitCustomDirectories() {
|
||||||
|
OutputLayout layout = new OutputLayoutPolicy().custom(
|
||||||
|
tempDir,
|
||||||
|
tempDir.resolve("model-output"),
|
||||||
|
tempDir.resolve("xml-output"),
|
||||||
|
tempDir.resolve("mapper-output"));
|
||||||
|
|
||||||
|
assertEquals(tempDir.resolve("model-output").toAbsolutePath().normalize(),
|
||||||
|
layout.modelDirectory());
|
||||||
|
assertEquals(tempDir.resolve("xml-output").toAbsolutePath().normalize(),
|
||||||
|
layout.xmlDirectory());
|
||||||
|
assertEquals(tempDir.resolve("mapper-output").toAbsolutePath().normalize(),
|
||||||
|
layout.mapperDirectory());
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package com.example.workutils.biz.mybatisgenerator.application;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.FileTime;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class OutputSnapshotTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void classifiesCreatedUpdatedAndUnchangedFiles() throws IOException {
|
||||||
|
Path javaRoot = Files.createDirectories(tempDir.resolve("src/main/java"));
|
||||||
|
Path xmlRoot = Files.createDirectories(tempDir.resolve("src/main/resources"));
|
||||||
|
Path updated = Files.writeString(javaRoot.resolve("Account.java"), "before");
|
||||||
|
Path unchanged = Files.writeString(xmlRoot.resolve("Account.xml"), "same");
|
||||||
|
OutputLayout layout = new OutputLayout(tempDir, javaRoot, xmlRoot, javaRoot);
|
||||||
|
OutputSnapshot before = OutputSnapshot.capture(layout);
|
||||||
|
|
||||||
|
Files.writeString(updated, "after-content");
|
||||||
|
Files.setLastModifiedTime(updated, FileTime.fromMillis(
|
||||||
|
Files.getLastModifiedTime(updated).toMillis() + 2_000));
|
||||||
|
Path created = Files.writeString(javaRoot.resolve("AccountMapper.java"), "new");
|
||||||
|
OutputSnapshot after = OutputSnapshot.capture(layout);
|
||||||
|
|
||||||
|
GenerationResult result = before.compare(after, "generated");
|
||||||
|
|
||||||
|
assertEquals(java.util.List.of(created.toAbsolutePath().normalize()),
|
||||||
|
result.createdFiles());
|
||||||
|
assertEquals(java.util.List.of(updated.toAbsolutePath().normalize()),
|
||||||
|
result.updatedFiles());
|
||||||
|
assertEquals(java.util.List.of(unchanged.toAbsolutePath().normalize()),
|
||||||
|
result.unchangedFiles());
|
||||||
|
assertEquals("generated", result.generatorMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user