From 5ebfbbbbdfbce8dd1719286295bc32df3d99203a Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Tue, 9 Jun 2026 20:23:40 +0200 Subject: [PATCH] export and import json --- TODO.md | 3 +- file_combine/build.gradle | 2 +- state_machine_exporter/build.gradle | 7 +++- .../springstatemachineexporter/Main.java | 9 ++-- .../ast/app/AstTransitionParser.java | 10 ++--- .../ast/app/StateMachineAggregator.java | 4 +- .../ast/app/TransitionStateUtils.java | 4 +- .../ast/common/CodebaseContext.java | 2 +- .../command/ExporterCommand.java | 29 +++++++++++-- .../{ast/out => exporter}/Dot.java | 11 ++--- .../exporter/JsonExporter.java | 39 +++++++++++++++++ .../{ast/out => exporter}/PlantUml.java | 12 +++--- .../{ast/out => exporter}/Scxml.java | 11 ++--- .../StateMachineExporter.java | 7 ++-- .../{ast/app/domain => model}/Action.java | 2 +- .../{ast/app/domain => model}/Guard.java | 2 +- .../{ast/app/domain => model}/State.java | 2 +- .../model/StateMachineModel.java | 20 +++++++++ .../{ast/app/domain => model}/Transition.java | 2 +- .../app/domain => model}/TransitionType.java | 2 +- .../service/ExportService.java | 38 +++++++++++------ .../service/JsonImportService.java | 26 ++++++++++++ .../RegressionTest.java | 10 ++--- .../ast/app/AstTransitionParserTest.java | 10 ++--- .../ast/app/StateMachineAggregatorTest.java | 4 +- .../ast/app/TransitionStateUtilsTest.java | 4 +- .../exporter/JsonExporterTest.java | 32 ++++++++++++++ .../service/JsonImportServiceTest.java | 42 +++++++++++++++++++ 28 files changed, 276 insertions(+), 70 deletions(-) rename state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/{ast/out => exporter}/Dot.java (96%) create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/JsonExporter.java rename state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/{ast/out => exporter}/PlantUml.java (94%) rename state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/{ast/out => exporter}/Scxml.java (91%) rename state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/{ast/out => exporter}/StateMachineExporter.java (72%) rename state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/{ast/app/domain => model}/Action.java (77%) rename state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/{ast/app/domain => model}/Guard.java (76%) rename state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/{ast/app/domain => model}/State.java (85%) create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/StateMachineModel.java rename state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/{ast/app/domain => model}/Transition.java (89%) rename state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/{ast/app/domain => model}/TransitionType.java (90%) create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/JsonImportService.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/exporter/JsonExporterTest.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/service/JsonImportServiceTest.java diff --git a/TODO.md b/TODO.md index d3d8409..c620abf 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,7 @@ 1.extract from all possible formats (ask chatgpt) -json, xml, maybe yaml +[DONE] json +[TODO] xml, maybe yaml 2. [PLAN] Support external dependencies (JARs/compiled classes): - Enable binding resolution in ASTParser (`setResolveBindings(true)`). diff --git a/file_combine/build.gradle b/file_combine/build.gradle index a5ad030..c1d4ba7 100644 --- a/file_combine/build.gradle +++ b/file_combine/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'application' - id 'com.gradleup.shadow' version '8.3.5' + id 'com.gradleup.shadow' version '9.0.0' } group = 'click.kamil.file_combine' diff --git a/state_machine_exporter/build.gradle b/state_machine_exporter/build.gradle index 2b57e3b..7d6cddb 100644 --- a/state_machine_exporter/build.gradle +++ b/state_machine_exporter/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'application' - id 'com.gradleup.shadow' version '8.3.5' + id 'com.gradleup.shadow' version '9.0.0' } group = 'click.kamil.springstatemachineexporter' @@ -32,6 +32,11 @@ dependencies { // deps for parsing java AST implementation 'org.eclipse.jdt:org.eclipse.jdt.core:3.36.0' + // Jackson for JSON support + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.1' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1' + compileOnly 'org.projectlombok:lombok:1.18.46' annotationProcessor 'org.projectlombok:lombok:1.18.46' diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java index 17efd24..7902a5a 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java @@ -1,8 +1,9 @@ package click.kamil.springstatemachineexporter; -import click.kamil.springstatemachineexporter.ast.out.Dot; -import click.kamil.springstatemachineexporter.ast.out.PlantUml; -import click.kamil.springstatemachineexporter.ast.out.Scxml; +import click.kamil.springstatemachineexporter.exporter.Dot; +import click.kamil.springstatemachineexporter.exporter.JsonExporter; +import click.kamil.springstatemachineexporter.exporter.PlantUml; +import click.kamil.springstatemachineexporter.exporter.Scxml; import click.kamil.springstatemachineexporter.command.ExporterCommand; import click.kamil.springstatemachineexporter.service.ExportService; import picocli.CommandLine; @@ -12,7 +13,7 @@ import java.util.List; public class Main { public static void main(String[] args) { // Manual DI / Wiring - var exporters = List.of(new PlantUml(), new Dot(), new Scxml()); + var exporters = List.of(new PlantUml(), new Dot(), new Scxml(), new JsonExporter()); var exportService = new ExportService(exporters); var command = new ExporterCommand(exportService); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/AstTransitionParser.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/AstTransitionParser.java index b37ae37..21c8546 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/AstTransitionParser.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/AstTransitionParser.java @@ -1,10 +1,10 @@ package click.kamil.springstatemachineexporter.ast.app; -import click.kamil.springstatemachineexporter.ast.app.domain.Action; -import click.kamil.springstatemachineexporter.ast.app.domain.Guard; -import click.kamil.springstatemachineexporter.ast.app.domain.State; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; -import click.kamil.springstatemachineexporter.ast.app.domain.TransitionType; +import click.kamil.springstatemachineexporter.model.Action; +import click.kamil.springstatemachineexporter.model.Guard; +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; +import click.kamil.springstatemachineexporter.model.TransitionType; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.ClassInstanceCreation; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/StateMachineAggregator.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/StateMachineAggregator.java index 7272398..f2a95ca 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/StateMachineAggregator.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/StateMachineAggregator.java @@ -1,7 +1,7 @@ package click.kamil.springstatemachineexporter.ast.app; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; -import click.kamil.springstatemachineexporter.ast.app.domain.TransitionType; +import click.kamil.springstatemachineexporter.model.Transition; +import click.kamil.springstatemachineexporter.model.TransitionType; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import lombok.Getter; import org.eclipse.jdt.core.dom.ArrayInitializer; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/TransitionStateUtils.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/TransitionStateUtils.java index cb762b8..c977ed7 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/TransitionStateUtils.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/TransitionStateUtils.java @@ -1,7 +1,7 @@ package click.kamil.springstatemachineexporter.ast.app; -import click.kamil.springstatemachineexporter.ast.app.domain.State; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; import java.util.Collection; import java.util.Objects; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java index 6f929b1..5508593 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java @@ -1,6 +1,6 @@ package click.kamil.springstatemachineexporter.ast.common; -import click.kamil.springstatemachineexporter.ast.app.domain.State; +import click.kamil.springstatemachineexporter.model.State; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.Annotation; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/command/ExporterCommand.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/command/ExporterCommand.java index 458892e..c99ab4b 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/command/ExporterCommand.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/command/ExporterCommand.java @@ -7,6 +7,7 @@ import picocli.CommandLine.Command; import picocli.CommandLine.Option; import java.nio.file.Path; +import java.util.List; import java.util.concurrent.Callable; @Command( @@ -29,27 +30,49 @@ public class ExporterCommand implements Callable { @CommandLine.Spec CommandLine.Model.CommandSpec spec; - @Option(names = {"-i", "--input"}, description = "Input directory containing the Spring Boot project source code.", defaultValue = ".") + @Option(names = {"-i", "--input"}, description = "Input directory containing the Spring Boot project source code.") private Path inputDir; + @Option(names = {"-j", "--json"}, description = "Input JSON file containing the state machine definition.") + private Path jsonFile; + @Option(names = {"-o", "--output"}, description = "Output directory for the generated diagrams.", defaultValue = "./out") private Path outputDir; + @Option(names = {"-f", "--format"}, description = "Comma-separated list of formats to export (dot, puml, scxml, json). If omitted, all formats are exported.", split = ",") + private List selectedFormats; + @Override public Integer call() throws Exception { var out = spec.commandLine().getOut(); var err = spec.commandLine().getErr(); + if (inputDir == null && jsonFile == null) { + inputDir = Path.of("."); + } + out.println(CommandLine.Help.Ansi.AUTO.string("@|bold,green Starting export process...|@")); - out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Input Directory:|@ " + inputDir.toAbsolutePath())); + if (jsonFile != null) { + out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Input JSON File:|@ " + jsonFile.toAbsolutePath())); + } else { + out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Input Directory:|@ " + inputDir.toAbsolutePath())); + } out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Output Directory:|@ " + outputDir.toAbsolutePath())); + if (selectedFormats != null && !selectedFormats.isEmpty()) { + out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Selected Formats:|@ " + String.join(", ", selectedFormats))); + } try { - exportService.runExporter(inputDir, outputDir); + if (jsonFile != null) { + exportService.runJsonExporter(jsonFile, outputDir, selectedFormats); + } else { + exportService.runExporter(inputDir, outputDir, selectedFormats); + } out.println(CommandLine.Help.Ansi.AUTO.string("%n@|bold,green SUCCESS:|@ All state machines have been exported to " + outputDir.toAbsolutePath())); return 0; } catch (Exception e) { err.println(CommandLine.Help.Ansi.AUTO.string("%n@|bold,red ERROR:|@ An error occurred during the export process: " + e.getMessage())); + e.printStackTrace(err); return 1; } } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/Dot.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/Dot.java similarity index 96% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/Dot.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/Dot.java index 1f346ea..2e652b5 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/Dot.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/Dot.java @@ -1,8 +1,8 @@ -package click.kamil.springstatemachineexporter.ast.out; +package click.kamil.springstatemachineexporter.exporter; -import click.kamil.springstatemachineexporter.ast.app.domain.State; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; -import click.kamil.springstatemachineexporter.ast.app.domain.TransitionType; +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; +import click.kamil.springstatemachineexporter.model.TransitionType; import java.util.HashMap; import java.util.LinkedHashSet; @@ -18,7 +18,8 @@ public class Dot implements StateMachineExporter { }; @Override - public String export(List transitions, + public String export(String name, + List transitions, Set startStates, Set endStates, boolean useLambdaGuards) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/JsonExporter.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/JsonExporter.java new file mode 100644 index 0000000..3748e66 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/JsonExporter.java @@ -0,0 +1,39 @@ +package click.kamil.springstatemachineexporter.exporter; + +import click.kamil.springstatemachineexporter.model.StateMachineModel; +import click.kamil.springstatemachineexporter.model.Transition; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import java.util.List; +import java.util.Set; + +public class JsonExporter implements StateMachineExporter { + + private final ObjectMapper objectMapper; + + public JsonExporter() { + this.objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT); + } + + @Override + public String export(String name, List transitions, Set startStates, Set endStates, boolean useLambdaGuards) { + try { + StateMachineModel model = StateMachineModel.builder() + .name(name) + .transitions(transitions) + .startStates(startStates) + .endStates(endStates) + .build(); + return objectMapper.writeValueAsString(model); + } catch (Exception e) { + throw new RuntimeException("Failed to export to JSON", e); + } + } + + @Override + public String getFileExtension() { + return ".json"; + } +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/PlantUml.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/PlantUml.java similarity index 94% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/PlantUml.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/PlantUml.java index a7d24f1..1dfd9d9 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/PlantUml.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/PlantUml.java @@ -1,8 +1,8 @@ -package click.kamil.springstatemachineexporter.ast.out; +package click.kamil.springstatemachineexporter.exporter; -import click.kamil.springstatemachineexporter.ast.app.domain.State; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; -import click.kamil.springstatemachineexporter.ast.app.domain.TransitionType; +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; +import click.kamil.springstatemachineexporter.model.TransitionType; import click.kamil.springstatemachineexporter.ast.common.StringUtils; import java.util.ArrayList; @@ -34,10 +34,12 @@ public class PlantUml implements StateMachineExporter { private final String LAMBDA = "λ"; @Override - public String export(List transitions, + public String export(String name, + List transitions, Set startStates, Set endStates, boolean useLambdaGuards) { + StringBuilder sb = new StringBuilder(); sb.append("@startuml\n"); sb.append("skinparam state {\n"); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/Scxml.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/Scxml.java similarity index 91% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/Scxml.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/Scxml.java index 38f7c20..916dc47 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/Scxml.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/Scxml.java @@ -1,8 +1,8 @@ -package click.kamil.springstatemachineexporter.ast.out; +package click.kamil.springstatemachineexporter.exporter; -import click.kamil.springstatemachineexporter.ast.app.domain.State; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; -import click.kamil.springstatemachineexporter.ast.app.domain.TransitionType; +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; +import click.kamil.springstatemachineexporter.model.TransitionType; import java.util.HashSet; import java.util.List; @@ -13,7 +13,8 @@ public class Scxml implements StateMachineExporter { private static final String LAMBDA = "λ"; @Override - public String export(List transitions, + public String export(String name, + List transitions, Set startStates, Set endStates, boolean useLambdaGuards) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/StateMachineExporter.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/StateMachineExporter.java similarity index 72% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/StateMachineExporter.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/StateMachineExporter.java index 518096d..92c723c 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/out/StateMachineExporter.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/exporter/StateMachineExporter.java @@ -1,12 +1,13 @@ -package click.kamil.springstatemachineexporter.ast.out; +package click.kamil.springstatemachineexporter.exporter; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; +import click.kamil.springstatemachineexporter.model.Transition; import java.util.List; import java.util.Set; public interface StateMachineExporter { - String export(List transitions, + String export(String name, + List transitions, Set startStates, Set endStates, boolean useLambdaGuards); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/Action.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/Action.java similarity index 77% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/Action.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/Action.java index 08c3162..d0abee1 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/Action.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/Action.java @@ -1,4 +1,4 @@ -package click.kamil.springstatemachineexporter.ast.app.domain; +package click.kamil.springstatemachineexporter.model; public record Action(String expression, boolean isLambda) { public static Action of(String expression, boolean isLambda) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/Guard.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/Guard.java similarity index 76% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/Guard.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/Guard.java index a82c100..e0db32e 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/Guard.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/Guard.java @@ -1,4 +1,4 @@ -package click.kamil.springstatemachineexporter.ast.app.domain; +package click.kamil.springstatemachineexporter.model; public record Guard(String expression, boolean isLambda) { public static Guard of(String expression, boolean isLambda) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/State.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/State.java similarity index 85% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/State.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/State.java index d8684c7..f28fe4b 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/State.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/State.java @@ -1,4 +1,4 @@ -package click.kamil.springstatemachineexporter.ast.app.domain; +package click.kamil.springstatemachineexporter.model; public record State(String rawName, String fullIdentifier) { public static State of(String name) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/StateMachineModel.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/StateMachineModel.java new file mode 100644 index 0000000..21dc2ed --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/StateMachineModel.java @@ -0,0 +1,20 @@ +package click.kamil.springstatemachineexporter.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Set; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class StateMachineModel { + private String name; + private List transitions; + private Set startStates; + private Set endStates; +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/Transition.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/Transition.java similarity index 89% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/Transition.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/Transition.java index 138fb3a..318388b 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/Transition.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/Transition.java @@ -1,4 +1,4 @@ -package click.kamil.springstatemachineexporter.ast.app.domain; +package click.kamil.springstatemachineexporter.model; import lombok.Getter; import lombok.Setter; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/TransitionType.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/TransitionType.java similarity index 90% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/TransitionType.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/TransitionType.java index 7c55fcf..3a72002 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/app/domain/TransitionType.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/model/TransitionType.java @@ -1,4 +1,4 @@ -package click.kamil.springstatemachineexporter.ast.app.domain; +package click.kamil.springstatemachineexporter.model; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java index b9605a7..212b36e 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java @@ -3,9 +3,10 @@ package click.kamil.springstatemachineexporter.service; import click.kamil.springstatemachineexporter.ast.app.AstTransitionParser; import click.kamil.springstatemachineexporter.ast.app.StateMachineAggregator; import click.kamil.springstatemachineexporter.ast.app.TransitionStateUtils; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; +import click.kamil.springstatemachineexporter.model.StateMachineModel; +import click.kamil.springstatemachineexporter.model.Transition; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; -import click.kamil.springstatemachineexporter.ast.out.StateMachineExporter; +import click.kamil.springstatemachineexporter.exporter.StateMachineExporter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.jdt.core.dom.MethodDeclaration; @@ -27,13 +28,19 @@ public class ExportService { private static final List TARGET_ANNOTATIONS = List.of("EnableStateMachineFactory", "EnableStateMachine"); public static final Set STATE_MACHINE_RETURN_TYPES = Set.of("StateMachine", "StateMachineFactory", "StateMachineModelFactory"); - public void runExporter(Path inputDir, Path outputDir) throws IOException { + public void runExporter(Path inputDir, Path outputDir, List selectedFormats) throws IOException { CodebaseContext context = new CodebaseContext(); context.scan(inputDir); - exportAll(context, outputDir); + exportAll(context, outputDir, selectedFormats); } - private void exportAll(CodebaseContext context, Path outputDir) throws IOException { + public void runJsonExporter(Path jsonFile, Path outputDir, List selectedFormats) throws IOException { + JsonImportService jsonImportService = new JsonImportService(); + StateMachineModel model = jsonImportService.importJson(jsonFile); + generateOutputs(outputDir, model.getName(), model.getTransitions(), model.getStartStates(), model.getEndStates(), selectedFormats); + } + + private void exportAll(CodebaseContext context, Path outputDir, List selectedFormats) throws IOException { Set processedLocations = new HashSet<>(); // 1. Find entry point classes (annotated or extending adapter) @@ -41,7 +48,7 @@ public class ExportService { for (TypeDeclaration td : entryPoints) { String fqn = context.getFqn(td); if (processedLocations.add(fqn)) { - processEntryPoint(td, outputDir, context); + processEntryPoint(td, outputDir, context, selectedFormats); } } @@ -54,12 +61,12 @@ public class ExportService { String uniqueId = parentFqn + "#" + methodName; if (processedLocations.add(uniqueId)) { - processBeanMethod(m, outputDir, context); + processBeanMethod(m, outputDir, context, selectedFormats); } } } - private void processEntryPoint(TypeDeclaration td, Path outputDir, CodebaseContext context) throws IOException { + private void processEntryPoint(TypeDeclaration td, Path outputDir, CodebaseContext context, List selectedFormats) throws IOException { String className = context.getFqn(td); log.info("Processing state machine config: {}", className); @@ -76,10 +83,10 @@ public class ExportService { Set startStates = TransitionStateUtils.findStartStates(transitions, initialStatesAst); Set endStates = TransitionStateUtils.findEndStates(transitions, endStatesAst); - generateOutputs(outputDir, className, transitions, startStates, endStates); + generateOutputs(outputDir, className, transitions, startStates, endStates, selectedFormats); } - private void processBeanMethod(MethodDeclaration m, Path outputDir, CodebaseContext context) throws IOException { + private void processBeanMethod(MethodDeclaration m, Path outputDir, CodebaseContext context, List selectedFormats) throws IOException { TypeDeclaration parentClass = (TypeDeclaration) m.getParent(); String parentFqn = context.getFqn(parentClass); String methodName = m.getName().getIdentifier(); @@ -92,17 +99,22 @@ public class ExportService { Set startStates = TransitionStateUtils.findStartStates(transitions); Set endStates = TransitionStateUtils.findEndStates(transitions); - generateOutputs(outputDir, uniqueName, transitions, startStates, endStates); + generateOutputs(outputDir, uniqueName, transitions, startStates, endStates, selectedFormats); } private void generateOutputs(Path outputDir, String baseName, List transitions, - Set startStates, Set endStates) throws IOException { + Set startStates, Set endStates, List selectedFormats) throws IOException { Path targetDir = outputDir.resolve(baseName); Files.createDirectories(targetDir); for (StateMachineExporter output : exporters) { - String content = output.export(transitions, startStates, endStates, true); + if (selectedFormats != null && !selectedFormats.isEmpty()) { + boolean match = selectedFormats.stream().anyMatch(f -> output.getFileExtension().toLowerCase().contains(f.toLowerCase())); + if (!match) continue; + } + + String content = output.export(baseName, transitions, startStates, endStates, true); String fileName = baseName + output.getFileExtension(); try (PrintWriter out = new PrintWriter(targetDir.resolve(fileName).toFile())) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/JsonImportService.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/JsonImportService.java new file mode 100644 index 0000000..6177645 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/JsonImportService.java @@ -0,0 +1,26 @@ +package click.kamil.springstatemachineexporter.service; + +import click.kamil.springstatemachineexporter.model.StateMachineModel; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.nio.file.Path; + +public class JsonImportService { + + private final ObjectMapper objectMapper; + + public JsonImportService() { + this.objectMapper = new ObjectMapper(); + } + + public StateMachineModel importJson(Path jsonFile) throws IOException { + StateMachineModel model = objectMapper.readValue(jsonFile.toFile(), StateMachineModel.class); + if (model.getName() == null) { + String fileName = jsonFile.getFileName().toString(); + int dot = fileName.lastIndexOf('.'); + model.setName(dot > 0 ? fileName.substring(0, dot) : fileName); + } + return model; + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java index 33370ad..09b524b 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java @@ -1,9 +1,9 @@ package click.kamil.springstatemachineexporter; -import click.kamil.springstatemachineexporter.ast.out.Dot; -import click.kamil.springstatemachineexporter.ast.out.PlantUml; -import click.kamil.springstatemachineexporter.ast.out.Scxml; -import click.kamil.springstatemachineexporter.ast.out.StateMachineExporter; +import click.kamil.springstatemachineexporter.exporter.Dot; +import click.kamil.springstatemachineexporter.exporter.PlantUml; +import click.kamil.springstatemachineexporter.exporter.Scxml; +import click.kamil.springstatemachineexporter.exporter.StateMachineExporter; import click.kamil.springstatemachineexporter.service.ExportService; import click.kamil.springstatemachineexporter.utils.TestScenario; import org.junit.jupiter.api.io.TempDir; @@ -95,7 +95,7 @@ public class RegressionTest { @ParameterizedTest(name = "{0}") @MethodSource("provideTestScenarios") void testOutputMatchesGolden(TestScenario scenario, @TempDir Path tempDir) throws Exception { - exportService.runExporter(scenario.inputPath(), tempDir); + exportService.runExporter(scenario.inputPath(), tempDir, null); // Find the generated directory (it might be named with FQN) List generatedDirs; diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/AstTransitionParserTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/AstTransitionParserTest.java index c2d4871..7475f87 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/AstTransitionParserTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/AstTransitionParserTest.java @@ -1,10 +1,10 @@ package click.kamil.springstatemachineexporter.ast.app; -import click.kamil.springstatemachineexporter.ast.app.domain.Action; -import click.kamil.springstatemachineexporter.ast.app.domain.Guard; -import click.kamil.springstatemachineexporter.ast.app.domain.State; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; -import click.kamil.springstatemachineexporter.ast.app.domain.TransitionType; +import click.kamil.springstatemachineexporter.model.Action; +import click.kamil.springstatemachineexporter.model.Guard; +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; +import click.kamil.springstatemachineexporter.model.TransitionType; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/StateMachineAggregatorTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/StateMachineAggregatorTest.java index 5e7d690..74dc872 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/StateMachineAggregatorTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/StateMachineAggregatorTest.java @@ -1,7 +1,7 @@ package click.kamil.springstatemachineexporter.ast.app; -import click.kamil.springstatemachineexporter.ast.app.domain.State; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.junit.jupiter.api.BeforeEach; diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/TransitionStateUtilsTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/TransitionStateUtilsTest.java index fc28e4a..c7f6b67 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/TransitionStateUtilsTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/app/TransitionStateUtilsTest.java @@ -1,7 +1,7 @@ package click.kamil.springstatemachineexporter.ast.app; -import click.kamil.springstatemachineexporter.ast.app.domain.State; -import click.kamil.springstatemachineexporter.ast.app.domain.Transition; +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/exporter/JsonExporterTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/exporter/JsonExporterTest.java new file mode 100644 index 0000000..2b5b3e5 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/exporter/JsonExporterTest.java @@ -0,0 +1,32 @@ +package click.kamil.springstatemachineexporter.exporter; + +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; +import click.kamil.springstatemachineexporter.model.TransitionType; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class JsonExporterTest { + + @Test + void shouldExportToJson() { + JsonExporter exporter = new JsonExporter(); + Transition transition = new Transition(); + transition.setType(TransitionType.EXTERNAL); + transition.setSourceStates(List.of(State.of("S1"))); + transition.setTargetStates(List.of(State.of("S2"))); + transition.setEvent("E1"); + + String json = exporter.export("TestSM", List.of(transition), Set.of("S1"), Set.of("S2"), true); + + assertThat(json) + .contains("\"name\" : \"TestSM\"") + .contains("\"event\" : \"E1\"") + .contains("\"rawName\" : \"S1\"") + .contains("\"rawName\" : \"S2\""); + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/service/JsonImportServiceTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/service/JsonImportServiceTest.java new file mode 100644 index 0000000..ae903e9 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/service/JsonImportServiceTest.java @@ -0,0 +1,42 @@ +package click.kamil.springstatemachineexporter.service; + +import click.kamil.springstatemachineexporter.model.StateMachineModel; +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.assertj.core.api.Assertions.assertThat; + +class JsonImportServiceTest { + + @Test + void shouldImportFromJson(@TempDir Path tempDir) throws IOException { + Path jsonFile = tempDir.resolve("test.json"); + String json = """ + { + "name" : "ImportedSM", + "transitions" : [ { + "type" : "EXTERNAL", + "sourceStates" : [ { "rawName" : "S1", "fullIdentifier" : "S1" } ], + "targetStates" : [ { "rawName" : "S2", "fullIdentifier" : "S2" } ], + "event" : "E1" + } ], + "startStates" : [ "S1" ], + "endStates" : [ "S2" ] + } + """; + Files.writeString(jsonFile, json); + + JsonImportService importService = new JsonImportService(); + StateMachineModel model = importService.importJson(jsonFile); + + assertThat(model.getName()).isEqualTo("ImportedSM"); + assertThat(model.getTransitions()).hasSize(1); + assertThat(model.getTransitions().get(0).getEvent()).isEqualTo("E1"); + assertThat(model.getStartStates()).containsExactly("S1"); + assertThat(model.getEndStates()).containsExactly("S2"); + } +}