Precompute matched-transition link keys for HTML export
Embed linkKey on matchedTransitions in HTML metadata so explorer highlighting uses the same SVG #link_* suffix as PlantUML, avoiding JS/Java format drift while keeping golden JSON unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,16 +1,20 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@Builder(toBuilder = true)
|
||||
@Jacksonized
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class MatchedTransition {
|
||||
private final String sourceState;
|
||||
private final String targetState;
|
||||
private final String event;
|
||||
/** Precomputed {@code #link_*} suffix for HTML/SVG highlight; optional in JSON exports. */
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private final String linkKey;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package click.kamil.springstatemachineexporter.exporter;
|
||||
|
||||
/**
|
||||
* Builds SVG/HTML transition link keys ({@code Source__Event}) using the same formatting rules as
|
||||
* {@link PlantUml} embedded identifiers and the HTML explorer {@code buildLinkKey} helper.
|
||||
*/
|
||||
public final class TransitionLinkKey {
|
||||
|
||||
private TransitionLinkKey() {
|
||||
}
|
||||
|
||||
public static String build(String sourceStateIdentifier, String eventIdentifier) {
|
||||
return build(sourceStateIdentifier, eventIdentifier, EnumFormat.fn, EnumFormat.fn);
|
||||
}
|
||||
|
||||
public static String build(
|
||||
String sourceStateIdentifier,
|
||||
String eventIdentifier,
|
||||
EnumFormat stateFormat,
|
||||
EnumFormat eventFormat) {
|
||||
if (eventIdentifier == null || eventIdentifier.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
String formattedEvent = formatIdentifier(eventIdentifier, eventFormat);
|
||||
if (sourceStateIdentifier == null || sourceStateIdentifier.isBlank()) {
|
||||
return "__" + normalize(formattedEvent);
|
||||
}
|
||||
return normalize(formatIdentifier(sourceStateIdentifier, stateFormat))
|
||||
+ "__"
|
||||
+ normalize(formattedEvent);
|
||||
}
|
||||
|
||||
static String formatIdentifier(String identifier, EnumFormat format) {
|
||||
if (identifier == null || identifier.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
return switch (format) {
|
||||
case fqn -> identifier;
|
||||
case sn -> {
|
||||
int lastDot = identifier.lastIndexOf('.');
|
||||
yield lastDot >= 0 ? identifier.substring(lastDot + 1) : identifier;
|
||||
}
|
||||
case fn -> {
|
||||
int lastDot = identifier.lastIndexOf('.');
|
||||
if (lastDot <= 0) {
|
||||
yield identifier;
|
||||
}
|
||||
int prevDot = identifier.lastIndexOf('.', lastDot - 1);
|
||||
yield prevDot > 0 ? identifier.substring(prevDot + 1) : identifier;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static String normalize(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
return value.replaceAll("[^a-zA-Z0-9]", "_");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package click.kamil.springstatemachineexporter.exporter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TransitionLinkKeyTest {
|
||||
|
||||
@Test
|
||||
void shouldBuildFnFormLinkKeyFromPackageCanonicalIdentifiers() {
|
||||
assertThat(TransitionLinkKey.build(
|
||||
"com.example.order.OrderState.NEW",
|
||||
"com.example.order.OrderEvent.PAY"))
|
||||
.isEqualTo("OrderState_NEW__OrderEvent_PAY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBuildFqnFormLinkKeyWhenRequested() {
|
||||
assertThat(TransitionLinkKey.build(
|
||||
"com.example.order.OrderState.NEW",
|
||||
"com.example.order.OrderEvent.PAY",
|
||||
EnumFormat.fqn,
|
||||
EnumFormat.fqn))
|
||||
.isEqualTo("com_example_order_OrderState_NEW__com_example_order_OrderEvent_PAY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBuildEventOnlyLinkKeyWhenSourceMissing() {
|
||||
assertThat(TransitionLinkKey.build(null, "com.example.order.OrderEvent.PAY"))
|
||||
.isEqualTo("__OrderEvent_PAY");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user