From 2af877e98f6923042f68890edb06ffcf7be31f43 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Mon, 29 Jun 2026 23:59:10 +0200 Subject: [PATCH 01/16] feat(generator): add DiffVisitor/DiffTraverser base infrastructure (C36 Phase 1) Base classes for paired tree traversal: - CollectionDiff with Entry/MatchedPair for collection comparison results - PairingStrategy interface with KeyPairingStrategy (maps) and IndexPairingStrategy (lists) - DiffVisitor abstract base with field-level diff methods and strategy selection callback - AbstractDiffTraverser with shared diffMap/diffList/diffEntityField helpers and auto-recursion into matched pairs --- .../visitors/diff/AbstractDiffTraverser.java | 83 +++++++++++++++++++ .../base/visitors/diff/CollectionDiff.java | 70 ++++++++++++++++ .../umg/base/visitors/diff/DiffVisitor.java | 64 ++++++++++++++ .../visitors/diff/IndexPairingStrategy.java | 51 ++++++++++++ .../visitors/diff/KeyPairingStrategy.java | 43 ++++++++++ .../base/visitors/diff/PairingStrategy.java | 12 +++ 6 files changed, 323 insertions(+) create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/CollectionDiff.java create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java new file mode 100644 index 000000000..8ba31e7a1 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -0,0 +1,83 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.List; +import java.util.Map; + +import io.apitomy.umg.base.Any; +import io.apitomy.umg.base.Node; +import io.apitomy.umg.base.union.Union; + +/** + * Base class for generated diff traversers. Provides shared collection diffing logic. + * Subclasses (generated per spec version) provide entity-specific field iteration. + */ +public abstract class AbstractDiffTraverser { + + protected final DiffVisitor visitor; + + protected AbstractDiffTraverser(DiffVisitor visitor) { + this.visitor = visitor; + } + + @SuppressWarnings("unchecked") + protected void diffMap(String propertyName, Map original, Map updated) { + PairingStrategy strategy = + (PairingStrategy) visitor.getPairingStrategy(propertyName); + if (strategy == null) { + strategy = new KeyPairingStrategy<>(); + } + + CollectionDiff diff = strategy.pair( + original != null ? original : Map.of(), + updated != null ? updated : Map.of()); + visitor.diffMap(propertyName, diff); + + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + recurseIntoPair(pair.getOriginal(), pair.getUpdated()); + } + } + + @SuppressWarnings("unchecked") + protected void diffList(String propertyName, List original, List updated) { + PairingStrategy strategy = + (PairingStrategy) visitor.getPairingStrategy(propertyName); + if (strategy == null) { + strategy = new IndexPairingStrategy<>(); + } + + CollectionDiff diff = strategy.pair( + IndexPairingStrategy.toIndexMap(original), + IndexPairingStrategy.toIndexMap(updated)); + visitor.diffList(propertyName, diff); + + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + recurseIntoPair(pair.getOriginal(), pair.getUpdated()); + } + } + + protected void diffEntityField(String propertyName, Node original, Node updated) { + visitor.diffEntity(propertyName, original, updated); + if (original != null && updated != null) { + traverseNode(original, updated); + } + } + + protected void diffUnionField(String propertyName, Union original, Union updated) { + visitor.diffUnion(propertyName, original, updated); + } + + private void recurseIntoPair(Object original, Object updated) { + if (original instanceof Node && updated instanceof Node) { + traverseNode((Node) original, (Node) updated); + } else if (original instanceof Union && updated instanceof Union) { + // Union pairs in collections — visitor handles dispatch + visitor.diffUnion(null, (Union) original, (Union) updated); + } + } + + /** + * Dispatches to the correct entity-specific traverse method. + * Generated subclasses override this with type-based dispatch. + */ + protected abstract void traverseNode(Node original, Node updated); +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/CollectionDiff.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/CollectionDiff.java new file mode 100644 index 000000000..8b96fd62e --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/CollectionDiff.java @@ -0,0 +1,70 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.ArrayList; +import java.util.List; + +/** + * Result of pairing two collections. Contains entries that were added, removed, + * or matched between the original and updated collections. + * + * @param the key type (String for maps, Integer for lists) + * @param the value type + */ +public class CollectionDiff { + + private final List> added; + private final List> removed; + private final List> matched; + + public CollectionDiff(List> added, List> removed, + List> matched) { + this.added = added; + this.removed = removed; + this.matched = matched; + } + + public List> getAdded() { + return added; + } + + public List> getRemoved() { + return removed; + } + + public List> getMatched() { + return matched; + } + + public boolean hasChanges() { + return !added.isEmpty() || !removed.isEmpty(); + } + + public static class Entry { + private final K key; + private final V value; + + public Entry(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { return key; } + public V getValue() { return value; } + } + + public static class MatchedPair { + private final K key; + private final V original; + private final V updated; + + public MatchedPair(K key, V original, V updated) { + this.key = key; + this.original = original; + this.updated = updated; + } + + public K getKey() { return key; } + public V getOriginal() { return original; } + public V getUpdated() { return updated; } + } +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java new file mode 100644 index 000000000..b747a63f6 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java @@ -0,0 +1,64 @@ +package io.apitomy.umg.base.visitors.diff; + +import io.apitomy.umg.base.Node; +import io.apitomy.umg.base.union.Union; + +/** + * Visitor for paired tree traversal. The generated DiffTraverser walks two trees + * in parallel, calling these methods for each field. Subclasses override the methods + * they care about. + * + *

The traverser auto-recurses into entity fields and matched collection pairs. + * Override {@link #visitEntityPair} to skip recursion for specific entities. + */ +public abstract class DiffVisitor { + + /** + * Called before traversing fields of an entity pair. Return false to skip + * field-level traversal of this pair. + */ + public boolean visitEntityPair(Node original, Node updated) { + return true; + } + + /** + * Called for each primitive-typed field (String, Boolean, Number, etc.). + */ + public void diffPrimitive(String propertyName, Object original, Object updated) { + } + + /** + * Called for each entity-typed field. The traverser auto-recurses into both + * entities after this call (if both are non-null). + */ + public void diffEntity(String propertyName, Node original, Node updated) { + } + + /** + * Called for each union-typed field. + */ + public void diffUnion(String propertyName, Union original, Union updated) { + } + + /** + * Called for each list-typed field with the pairing result. + * The traverser auto-recurses into matched entity/union pairs after this call. + */ + public void diffList(String propertyName, CollectionDiff diff) { + } + + /** + * Called for each map-typed field with the pairing result. + * The traverser auto-recurses into matched entity/union pairs after this call. + */ + public void diffMap(String propertyName, CollectionDiff diff) { + } + + /** + * Returns the pairing strategy for a collection field, or null to use the default + * (key-based for maps, index-based for lists). + */ + public PairingStrategy getPairingStrategy(String propertyName) { + return null; + } +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java new file mode 100644 index 000000000..26b528cca --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java @@ -0,0 +1,51 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Pairs list entries by index. Items at the same index are paired; + * extra items in the longer list are added/removed. + */ +public class IndexPairingStrategy implements PairingStrategy { + + @Override + public CollectionDiff pair(Map original, Map updated) { + Map orig = original != null ? original : new LinkedHashMap<>(); + Map upd = updated != null ? updated : new LinkedHashMap<>(); + + int maxIndex = Math.max(orig.size(), upd.size()); + + List> added = new ArrayList<>(); + List> removed = new ArrayList<>(); + List> matched = new ArrayList<>(); + + for (int i = 0; i < maxIndex; i++) { + boolean inOrig = orig.containsKey(i); + boolean inUpd = upd.containsKey(i); + if (inOrig && inUpd) { + matched.add(new CollectionDiff.MatchedPair<>(i, orig.get(i), upd.get(i))); + } else if (inUpd) { + added.add(new CollectionDiff.Entry<>(i, upd.get(i))); + } else { + removed.add(new CollectionDiff.Entry<>(i, orig.get(i))); + } + } + + return new CollectionDiff<>(added, removed, matched); + } + + /** + * Converts a list to an index-keyed map for use with this strategy. + */ + public static Map toIndexMap(List list) { + if (list == null) return new LinkedHashMap<>(); + Map map = new LinkedHashMap<>(); + for (int i = 0; i < list.size(); i++) { + map.put(i, list.get(i)); + } + return map; + } +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java new file mode 100644 index 000000000..d695c994c --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java @@ -0,0 +1,43 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Pairs map entries by key. Entries with matching keys are paired; + * keys present in only one map are added/removed. + */ +public class KeyPairingStrategy implements PairingStrategy { + + @Override + public CollectionDiff pair(Map original, Map updated) { + Map orig = original != null ? original : new LinkedHashMap<>(); + Map upd = updated != null ? updated : new LinkedHashMap<>(); + + Set allKeys = new LinkedHashSet<>(); + allKeys.addAll(orig.keySet()); + allKeys.addAll(upd.keySet()); + + List> added = new ArrayList<>(); + List> removed = new ArrayList<>(); + List> matched = new ArrayList<>(); + + for (String key : allKeys) { + boolean inOrig = orig.containsKey(key); + boolean inUpd = upd.containsKey(key); + if (inOrig && inUpd) { + matched.add(new CollectionDiff.MatchedPair<>(key, orig.get(key), upd.get(key))); + } else if (inUpd) { + added.add(new CollectionDiff.Entry<>(key, upd.get(key))); + } else { + removed.add(new CollectionDiff.Entry<>(key, orig.get(key))); + } + } + + return new CollectionDiff<>(added, removed, matched); + } +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java new file mode 100644 index 000000000..c8be11442 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java @@ -0,0 +1,12 @@ +package io.apitomy.umg.base.visitors.diff; + +/** + * Strategy for pairing elements from two collections to produce a {@link CollectionDiff}. + * + * @param the key type (String for maps, Integer for lists) + * @param the value type + */ +public interface PairingStrategy { + + CollectionDiff pair(java.util.Map original, java.util.Map updated); +} From 7a4bb389d6e59b6d775ad20af696c1c272eabb8a Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 00:41:43 +0200 Subject: [PATCH 02/16] feat(generator): DiffTraverser generation stage (C36 Phase 2) New CreateDiffTraversersStage generates per-spec-version diff traversers that walk two trees in parallel, dispatching to DiffVisitor methods: - diffPrimitive for primitive fields - diffEntityField for entity fields (auto-recurses) - diffUnionField for union fields - diffMap for map fields (pairs by key, auto-recurses matches) - diffList for list fields (pairs by index, auto-recurses matches) Registered in pipeline after CreateTraversersStage. Base classes registered in LoadBaseClassesStage. --- .../io/apitomy/umg/UnifiedModelGenerator.java | 2 + .../pipe/java/CreateDiffTraversersStage.java | 169 ++++++++++++++++++ .../umg/pipe/java/LoadBaseClassesStage.java | 6 + .../visitors/diff/AbstractDiffTraverser.java | 5 +- .../io/apitomy/umg/full/expected-manifest.txt | 25 +++ .../v1/visitors/Syn1DiffTraverser.java | 157 ++++++++++++++++ .../v2/visitors/Syn2DiffTraverser.java | 161 +++++++++++++++++ .../visitors/diff/AbstractDiffTraverser.java | 80 +++++++++ .../visitors/diff/CollectionDiff.java | 81 +++++++++ .../synthetic/visitors/diff/DiffVisitor.java | 65 +++++++ .../visitors/diff/IndexPairingStrategy.java | 52 ++++++ .../visitors/diff/KeyPairingStrategy.java | 43 +++++ .../visitors/diff/PairingStrategy.java | 15 ++ 13 files changed, 859 insertions(+), 2 deletions(-) create mode 100644 generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/CollectionDiff.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java diff --git a/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java b/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java index da237a875..3b50c94e0 100644 --- a/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java +++ b/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java @@ -58,6 +58,7 @@ import io.apitomy.umg.pipe.java.CreateReadersStage; import io.apitomy.umg.pipe.java.CreateTestFixturesStage; import io.apitomy.umg.pipe.java.CreateTraitInterfacesStage; +import io.apitomy.umg.pipe.java.CreateDiffTraversersStage; import io.apitomy.umg.pipe.java.CreateTraversersStage; import io.apitomy.umg.pipe.java.CreateCollectionUnionValuesStage; import io.apitomy.umg.pipe.java.CreatePrimitiveUnionValuesStage; @@ -163,6 +164,7 @@ public void generate() throws Exception { pipe.addStage(new CreateWriterDispatchersStage()); pipe.addStage(new CreateClonerDispatchersStage()); pipe.addStage(new CreateTraversersStage()); + pipe.addStage(new CreateDiffTraversersStage()); pipe.addStage(new CreateReaderFactoryStage()); pipe.addStage(new CreateWriterFactoryStage()); pipe.addStage(new CreateClonerFactoryStage()); diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java new file mode 100644 index 000000000..316986ea9 --- /dev/null +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -0,0 +1,169 @@ +package io.apitomy.umg.pipe.java; + +import java.util.Collection; +import java.util.stream.Collectors; + +import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.JavaInterfaceSource; +import org.jboss.forge.roaster.model.source.MethodSource; + +import io.apitomy.umg.beans.SpecificationVersion; +import io.apitomy.umg.models.concept.EntityModel; +import io.apitomy.umg.models.concept.PropertyModel; +import io.apitomy.umg.models.concept.PropertyModelWithOrigin; +import io.apitomy.umg.pipe.java.method.BodyBuilder; +import io.apitomy.umg.pipe.java.method.GetterMethod; + +/** + * Creates a DiffTraverser for each specification version. A DiffTraverser walks two + * trees of the same spec version in parallel, dispatching field-level diffs to a + * DiffVisitor. + */ +public class CreateDiffTraversersStage extends AbstractJavaStage { + + @Override + protected void doProcess() { + getState().getSpecIndex().getAllSpecificationVersions().forEach(this::createDiffTraverser); + } + + private void createDiffTraverser(SpecificationVersion specVer) { + String packageName = getTraverserPackageName(specVer); + String className = specVer.getPrefix() + "DiffTraverser"; + + debug("Creating diff traverser: " + className); + + JavaClassSource classSource = Roaster.create(JavaClassSource.class) + .setPackage(packageName) + .setName(className) + .setPublic(); + + String baseFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.AbstractDiffTraverser"; + JavaClassSource baseSource = getState().getJavaIndex().lookupClass(baseFQN); + classSource.addImport(baseSource); + classSource.extendSuperType(baseSource); + + String diffVisitorFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.DiffVisitor"; + JavaClassSource diffVisitorSource = getState().getJavaIndex().lookupClass(diffVisitorFQN); + classSource.addImport(diffVisitorSource); + + MethodSource constructor = classSource.addMethod() + .setConstructor(true).setPublic(); + constructor.addParameter("DiffVisitor", "visitor"); + constructor.setBody("super(visitor);"); + + String nodeFQN = getNodeEntityInterfaceFQN(); + JavaInterfaceSource nodeSource = getState().getJavaIndex().lookupInterface(nodeFQN); + classSource.addImport(nodeSource); + + java.util.Set createdMethods = new java.util.HashSet<>(); + + createTraverseNodeDispatch(specVer, classSource, createdMethods); + + specVer.getEntities().forEach(entity -> { + EntityModel entityModel = getState().getConceptIndex() + .lookupEntity(specVer.getNamespace() + "." + entity.getName()); + if (entityModel != null) { + String methodName = "traverse" + entityModel.getName(); + if (createdMethods.add(methodName)) { + createEntityTraverseMethod(specVer, classSource, entityModel); + } + } + }); + + getState().getJavaIndex().index(classSource); + } + + private void createTraverseNodeDispatch(SpecificationVersion specVer, JavaClassSource classSource, + java.util.Set createdMethods) { + createdMethods.add("traverseNode"); + MethodSource method = classSource.addMethod() + .setName("traverseNode") + .setReturnTypeVoid() + .setProtected(); + method.addParameter("Node", "original"); + method.addParameter("Node", "updated"); + method.addAnnotation(Override.class); + + BodyBuilder body = new BodyBuilder(); + body.append("Node target = original != null ? original : updated;"); + + boolean first = true; + for (var entity : specVer.getEntities()) { + EntityModel entityModel = getState().getConceptIndex() + .lookupEntity(specVer.getNamespace() + "." + entity.getName()); + if (entityModel == null) continue; + + JavaInterfaceSource javaEntity = lookupJavaEntity(entityModel); + classSource.addImport(javaEntity); + String entityType = javaEntity.getName(); + + body.addContext("entityType", entityType); + String methodName = "traverse" + entityModel.getName(); + body.addContext("methodName", methodName); + + if (first) { + body.append("if (target instanceof ${entityType}) {"); + first = false; + } else { + body.append("} else if (target instanceof ${entityType}) {"); + } + body.append(" this.${methodName}((${entityType}) original, (${entityType}) updated);"); + } + if (!first) { + body.append("}"); + } + + method.setBody(body.toString()); + } + + private void createEntityTraverseMethod(SpecificationVersion specVer, + JavaClassSource classSource, EntityModel entityModel) { + JavaInterfaceSource javaEntity = lookupJavaEntity(entityModel); + classSource.addImport(javaEntity); + + String entityType = javaEntity.getName(); + String methodName = "traverse" + entityModel.getName(); + + MethodSource method = classSource.addMethod() + .setName(methodName) + .setReturnTypeVoid() + .setPublic(); + method.addParameter(entityType, "original"); + method.addParameter(entityType, "updated"); + + Collection allProperties = + getState().getConceptIndex().getAllEntityProperties(entityModel); + + BodyBuilder body = new BodyBuilder(); + + body.append("if (original == null && updated == null) return;"); + body.append("if (!visitor.visitEntityPair(original, updated)) return;"); + body.append("if (original == null || updated == null) return;"); + + for (PropertyModelWithOrigin propWithOrigin : allProperties) { + PropertyModel property = propWithOrigin.getProperty(); + if (isStarProperty(property) || isRegexProperty(property)) { + continue; + } + + String getter = new GetterMethod(property).getName(); + body.addContext("propertyName", property.getName()); + body.addContext("getter", getter); + + if (isEntity(property)) { + body.append("this.diffEntityField(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + } else if (isEntityList(property) || isUnionList(property)) { + body.append("this.diffList(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + } else if (isEntityMap(property) || isUnionMap(property)) { + body.append("this.diffMap(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + } else if (isUnion(property)) { + body.append("this.diffUnionField(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + } else if (isPrimitive(property) || isPrimitiveList(property) || isPrimitiveMap(property)) { + body.append("visitor.diffPrimitive(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + } + } + + method.setBody(body.toString()); + } +} diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java index 64908ac2b..e973a927b 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java @@ -38,6 +38,11 @@ protected void doProcess() { "io.apitomy.umg.base.visitors.TraversalStep", "io.apitomy.umg.base.visitors.TraversalContextImpl", "io.apitomy.umg.base.visitors.ReverseTraverser", + "io.apitomy.umg.base.visitors.diff.AbstractDiffTraverser", + "io.apitomy.umg.base.visitors.diff.CollectionDiff", + "io.apitomy.umg.base.visitors.diff.DiffVisitor", + "io.apitomy.umg.base.visitors.diff.IndexPairingStrategy", + "io.apitomy.umg.base.visitors.diff.KeyPairingStrategy", "io.apitomy.umg.base.union.ListUnionValueImpl", "io.apitomy.umg.base.union.MapUnionValueImpl", "io.apitomy.umg.base.union.EntityListUnionValueImpl", @@ -64,6 +69,7 @@ protected void doProcess() { "io.apitomy.umg.base.union.MapUnionValue", "io.apitomy.umg.base.union.PrimitiveUnionValue", "io.apitomy.umg.base.union.StringListUnionValue", + "io.apitomy.umg.base.visitors.diff.PairingStrategy", "io.apitomy.umg.base.union.Union", "io.apitomy.umg.base.union.UnionValue" ); diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java index 8ba31e7a1..3a0551ec1 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -11,7 +11,7 @@ * Base class for generated diff traversers. Provides shared collection diffing logic. * Subclasses (generated per spec version) provide entity-specific field iteration. */ -public abstract class AbstractDiffTraverser { +public class AbstractDiffTraverser { protected final DiffVisitor visitor; @@ -79,5 +79,6 @@ private void recurseIntoPair(Object original, Object updated) { * Dispatches to the correct entity-specific traverse method. * Generated subclasses override this with type-based dispatch. */ - protected abstract void traverseNode(Node original, Node updated); + protected void traverseNode(Node original, Node updated) { + } } diff --git a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt index 35946a1d6..141aefdde 100644 --- a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt +++ b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt @@ -178,6 +178,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v20/io/AsyncApi20ModelReader.java io/apitomy/datamodels/models/asyncapi/v2x/v20/io/AsyncApi20ModelReaderDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v20/io/AsyncApi20ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v20/io/AsyncApi20ModelWriterDispatcher.java +io/apitomy/datamodels/models/asyncapi/v2x/v20/visitors/AsyncApi20DiffTraverser.java io/apitomy/datamodels/models/asyncapi/v2x/v20/visitors/AsyncApi20Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v20/visitors/AsyncApi20Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v20/visitors/AsyncApi20VisitorAdapter.java @@ -250,6 +251,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v21/io/AsyncApi21ModelReader.java io/apitomy/datamodels/models/asyncapi/v2x/v21/io/AsyncApi21ModelReaderDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v21/io/AsyncApi21ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v21/io/AsyncApi21ModelWriterDispatcher.java +io/apitomy/datamodels/models/asyncapi/v2x/v21/visitors/AsyncApi21DiffTraverser.java io/apitomy/datamodels/models/asyncapi/v2x/v21/visitors/AsyncApi21Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v21/visitors/AsyncApi21Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v21/visitors/AsyncApi21VisitorAdapter.java @@ -324,6 +326,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v22/io/AsyncApi22ModelReader.java io/apitomy/datamodels/models/asyncapi/v2x/v22/io/AsyncApi22ModelReaderDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v22/io/AsyncApi22ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v22/io/AsyncApi22ModelWriterDispatcher.java +io/apitomy/datamodels/models/asyncapi/v2x/v22/visitors/AsyncApi22DiffTraverser.java io/apitomy/datamodels/models/asyncapi/v2x/v22/visitors/AsyncApi22Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v22/visitors/AsyncApi22Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v22/visitors/AsyncApi22VisitorAdapter.java @@ -398,6 +401,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v23/io/AsyncApi23ModelReader.java io/apitomy/datamodels/models/asyncapi/v2x/v23/io/AsyncApi23ModelReaderDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v23/io/AsyncApi23ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v23/io/AsyncApi23ModelWriterDispatcher.java +io/apitomy/datamodels/models/asyncapi/v2x/v23/visitors/AsyncApi23DiffTraverser.java io/apitomy/datamodels/models/asyncapi/v2x/v23/visitors/AsyncApi23Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v23/visitors/AsyncApi23Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v23/visitors/AsyncApi23VisitorAdapter.java @@ -472,6 +476,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v24/io/AsyncApi24ModelReader.java io/apitomy/datamodels/models/asyncapi/v2x/v24/io/AsyncApi24ModelReaderDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v24/io/AsyncApi24ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v24/io/AsyncApi24ModelWriterDispatcher.java +io/apitomy/datamodels/models/asyncapi/v2x/v24/visitors/AsyncApi24DiffTraverser.java io/apitomy/datamodels/models/asyncapi/v2x/v24/visitors/AsyncApi24Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v24/visitors/AsyncApi24Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v24/visitors/AsyncApi24VisitorAdapter.java @@ -546,6 +551,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v25/io/AsyncApi25ModelReader.java io/apitomy/datamodels/models/asyncapi/v2x/v25/io/AsyncApi25ModelReaderDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v25/io/AsyncApi25ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v25/io/AsyncApi25ModelWriterDispatcher.java +io/apitomy/datamodels/models/asyncapi/v2x/v25/visitors/AsyncApi25DiffTraverser.java io/apitomy/datamodels/models/asyncapi/v2x/v25/visitors/AsyncApi25Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v25/visitors/AsyncApi25Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v25/visitors/AsyncApi25VisitorAdapter.java @@ -620,6 +626,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v26/io/AsyncApi26ModelReader.java io/apitomy/datamodels/models/asyncapi/v2x/v26/io/AsyncApi26ModelReaderDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v26/io/AsyncApi26ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v26/io/AsyncApi26ModelWriterDispatcher.java +io/apitomy/datamodels/models/asyncapi/v2x/v26/visitors/AsyncApi26DiffTraverser.java io/apitomy/datamodels/models/asyncapi/v2x/v26/visitors/AsyncApi26Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v26/visitors/AsyncApi26Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v26/visitors/AsyncApi26VisitorAdapter.java @@ -736,6 +743,7 @@ io/apitomy/datamodels/models/asyncapi/v3x/v30/io/AsyncApi30ModelReader.java io/apitomy/datamodels/models/asyncapi/v3x/v30/io/AsyncApi30ModelReaderDispatcher.java io/apitomy/datamodels/models/asyncapi/v3x/v30/io/AsyncApi30ModelWriter.java io/apitomy/datamodels/models/asyncapi/v3x/v30/io/AsyncApi30ModelWriterDispatcher.java +io/apitomy/datamodels/models/asyncapi/v3x/v30/visitors/AsyncApi30DiffTraverser.java io/apitomy/datamodels/models/asyncapi/v3x/v30/visitors/AsyncApi30Traverser.java io/apitomy/datamodels/models/asyncapi/v3x/v30/visitors/AsyncApi30Visitor.java io/apitomy/datamodels/models/asyncapi/v3x/v30/visitors/AsyncApi30VisitorAdapter.java @@ -815,6 +823,7 @@ io/apitomy/datamodels/models/asyncapi/v3x/v31/io/AsyncApi31ModelReader.java io/apitomy/datamodels/models/asyncapi/v3x/v31/io/AsyncApi31ModelReaderDispatcher.java io/apitomy/datamodels/models/asyncapi/v3x/v31/io/AsyncApi31ModelWriter.java io/apitomy/datamodels/models/asyncapi/v3x/v31/io/AsyncApi31ModelWriterDispatcher.java +io/apitomy/datamodels/models/asyncapi/v3x/v31/visitors/AsyncApi31DiffTraverser.java io/apitomy/datamodels/models/asyncapi/v3x/v31/visitors/AsyncApi31Traverser.java io/apitomy/datamodels/models/asyncapi/v3x/v31/visitors/AsyncApi31Visitor.java io/apitomy/datamodels/models/asyncapi/v3x/v31/visitors/AsyncApi31VisitorAdapter.java @@ -846,6 +855,7 @@ io/apitomy/datamodels/models/jsonschema/draft/draft4/io/JD4ModelReader.java io/apitomy/datamodels/models/jsonschema/draft/draft4/io/JD4ModelReaderDispatcher.java io/apitomy/datamodels/models/jsonschema/draft/draft4/io/JD4ModelWriter.java io/apitomy/datamodels/models/jsonschema/draft/draft4/io/JD4ModelWriterDispatcher.java +io/apitomy/datamodels/models/jsonschema/draft/draft4/visitors/JD4DiffTraverser.java io/apitomy/datamodels/models/jsonschema/draft/draft4/visitors/JD4Traverser.java io/apitomy/datamodels/models/jsonschema/draft/draft4/visitors/JD4Visitor.java io/apitomy/datamodels/models/jsonschema/draft/draft4/visitors/JD4VisitorAdapter.java @@ -858,6 +868,7 @@ io/apitomy/datamodels/models/jsonschema/draft/draft6/io/JD6ModelReader.java io/apitomy/datamodels/models/jsonschema/draft/draft6/io/JD6ModelReaderDispatcher.java io/apitomy/datamodels/models/jsonschema/draft/draft6/io/JD6ModelWriter.java io/apitomy/datamodels/models/jsonschema/draft/draft6/io/JD6ModelWriterDispatcher.java +io/apitomy/datamodels/models/jsonschema/draft/draft6/visitors/JD6DiffTraverser.java io/apitomy/datamodels/models/jsonschema/draft/draft6/visitors/JD6Traverser.java io/apitomy/datamodels/models/jsonschema/draft/draft6/visitors/JD6Visitor.java io/apitomy/datamodels/models/jsonschema/draft/draft6/visitors/JD6VisitorAdapter.java @@ -870,6 +881,7 @@ io/apitomy/datamodels/models/jsonschema/draft/draft7/io/JD7ModelReader.java io/apitomy/datamodels/models/jsonschema/draft/draft7/io/JD7ModelReaderDispatcher.java io/apitomy/datamodels/models/jsonschema/draft/draft7/io/JD7ModelWriter.java io/apitomy/datamodels/models/jsonschema/draft/draft7/io/JD7ModelWriterDispatcher.java +io/apitomy/datamodels/models/jsonschema/draft/draft7/visitors/JD7DiffTraverser.java io/apitomy/datamodels/models/jsonschema/draft/draft7/visitors/JD7Traverser.java io/apitomy/datamodels/models/jsonschema/draft/draft7/visitors/JD7Visitor.java io/apitomy/datamodels/models/jsonschema/draft/draft7/visitors/JD7VisitorAdapter.java @@ -885,6 +897,7 @@ io/apitomy/datamodels/models/jsonschema/modern/v201909/io/JM201909ModelReader.ja io/apitomy/datamodels/models/jsonschema/modern/v201909/io/JM201909ModelReaderDispatcher.java io/apitomy/datamodels/models/jsonschema/modern/v201909/io/JM201909ModelWriter.java io/apitomy/datamodels/models/jsonschema/modern/v201909/io/JM201909ModelWriterDispatcher.java +io/apitomy/datamodels/models/jsonschema/modern/v201909/visitors/JM201909DiffTraverser.java io/apitomy/datamodels/models/jsonschema/modern/v201909/visitors/JM201909Traverser.java io/apitomy/datamodels/models/jsonschema/modern/v201909/visitors/JM201909Visitor.java io/apitomy/datamodels/models/jsonschema/modern/v201909/visitors/JM201909VisitorAdapter.java @@ -897,6 +910,7 @@ io/apitomy/datamodels/models/jsonschema/modern/v202012/io/JM202012ModelReader.ja io/apitomy/datamodels/models/jsonschema/modern/v202012/io/JM202012ModelReaderDispatcher.java io/apitomy/datamodels/models/jsonschema/modern/v202012/io/JM202012ModelWriter.java io/apitomy/datamodels/models/jsonschema/modern/v202012/io/JM202012ModelWriterDispatcher.java +io/apitomy/datamodels/models/jsonschema/modern/v202012/visitors/JM202012DiffTraverser.java io/apitomy/datamodels/models/jsonschema/modern/v202012/visitors/JM202012Traverser.java io/apitomy/datamodels/models/jsonschema/modern/v202012/visitors/JM202012Visitor.java io/apitomy/datamodels/models/jsonschema/modern/v202012/visitors/JM202012VisitorAdapter.java @@ -1034,6 +1048,7 @@ io/apitomy/datamodels/models/openapi/v2x/v20/io/OpenApi20ModelReader.java io/apitomy/datamodels/models/openapi/v2x/v20/io/OpenApi20ModelReaderDispatcher.java io/apitomy/datamodels/models/openapi/v2x/v20/io/OpenApi20ModelWriter.java io/apitomy/datamodels/models/openapi/v2x/v20/io/OpenApi20ModelWriterDispatcher.java +io/apitomy/datamodels/models/openapi/v2x/v20/visitors/OpenApi20DiffTraverser.java io/apitomy/datamodels/models/openapi/v2x/v20/visitors/OpenApi20Traverser.java io/apitomy/datamodels/models/openapi/v2x/v20/visitors/OpenApi20Visitor.java io/apitomy/datamodels/models/openapi/v2x/v20/visitors/OpenApi20VisitorAdapter.java @@ -1144,6 +1159,7 @@ io/apitomy/datamodels/models/openapi/v3x/v30/io/OpenApi30ModelReader.java io/apitomy/datamodels/models/openapi/v3x/v30/io/OpenApi30ModelReaderDispatcher.java io/apitomy/datamodels/models/openapi/v3x/v30/io/OpenApi30ModelWriter.java io/apitomy/datamodels/models/openapi/v3x/v30/io/OpenApi30ModelWriterDispatcher.java +io/apitomy/datamodels/models/openapi/v3x/v30/visitors/OpenApi30DiffTraverser.java io/apitomy/datamodels/models/openapi/v3x/v30/visitors/OpenApi30Traverser.java io/apitomy/datamodels/models/openapi/v3x/v30/visitors/OpenApi30Visitor.java io/apitomy/datamodels/models/openapi/v3x/v30/visitors/OpenApi30VisitorAdapter.java @@ -1218,6 +1234,7 @@ io/apitomy/datamodels/models/openapi/v3x/v31/io/OpenApi31ModelReader.java io/apitomy/datamodels/models/openapi/v3x/v31/io/OpenApi31ModelReaderDispatcher.java io/apitomy/datamodels/models/openapi/v3x/v31/io/OpenApi31ModelWriter.java io/apitomy/datamodels/models/openapi/v3x/v31/io/OpenApi31ModelWriterDispatcher.java +io/apitomy/datamodels/models/openapi/v3x/v31/visitors/OpenApi31DiffTraverser.java io/apitomy/datamodels/models/openapi/v3x/v31/visitors/OpenApi31Traverser.java io/apitomy/datamodels/models/openapi/v3x/v31/visitors/OpenApi31Visitor.java io/apitomy/datamodels/models/openapi/v3x/v31/visitors/OpenApi31VisitorAdapter.java @@ -1304,6 +1321,7 @@ io/apitomy/datamodels/models/openrpc/v1x/v13/io/OpenRpc13ModelReader.java io/apitomy/datamodels/models/openrpc/v1x/v13/io/OpenRpc13ModelReaderDispatcher.java io/apitomy/datamodels/models/openrpc/v1x/v13/io/OpenRpc13ModelWriter.java io/apitomy/datamodels/models/openrpc/v1x/v13/io/OpenRpc13ModelWriterDispatcher.java +io/apitomy/datamodels/models/openrpc/v1x/v13/visitors/OpenRpc13DiffTraverser.java io/apitomy/datamodels/models/openrpc/v1x/v13/visitors/OpenRpc13Traverser.java io/apitomy/datamodels/models/openrpc/v1x/v13/visitors/OpenRpc13Visitor.java io/apitomy/datamodels/models/openrpc/v1x/v13/visitors/OpenRpc13VisitorAdapter.java @@ -1348,6 +1366,7 @@ io/apitomy/datamodels/models/openrpc/v1x/v14/io/OpenRpc14ModelReader.java io/apitomy/datamodels/models/openrpc/v1x/v14/io/OpenRpc14ModelReaderDispatcher.java io/apitomy/datamodels/models/openrpc/v1x/v14/io/OpenRpc14ModelWriter.java io/apitomy/datamodels/models/openrpc/v1x/v14/io/OpenRpc14ModelWriterDispatcher.java +io/apitomy/datamodels/models/openrpc/v1x/v14/visitors/OpenRpc14DiffTraverser.java io/apitomy/datamodels/models/openrpc/v1x/v14/visitors/OpenRpc14Traverser.java io/apitomy/datamodels/models/openrpc/v1x/v14/visitors/OpenRpc14Visitor.java io/apitomy/datamodels/models/openrpc/v1x/v14/visitors/OpenRpc14VisitorAdapter.java @@ -1390,3 +1409,9 @@ io/apitomy/datamodels/models/visitors/TraversalStepType.java io/apitomy/datamodels/models/visitors/Traverser.java io/apitomy/datamodels/models/visitors/TraversingVisitor.java io/apitomy/datamodels/models/visitors/Visitor.java +io/apitomy/datamodels/models/visitors/diff/AbstractDiffTraverser.java +io/apitomy/datamodels/models/visitors/diff/CollectionDiff.java +io/apitomy/datamodels/models/visitors/diff/DiffVisitor.java +io/apitomy/datamodels/models/visitors/diff/IndexPairingStrategy.java +io/apitomy/datamodels/models/visitors/diff/KeyPairingStrategy.java +io/apitomy/datamodels/models/visitors/diff/PairingStrategy.java diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java new file mode 100644 index 000000000..b727f16a1 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -0,0 +1,157 @@ +package io.test.synthetic.v1.visitors; + +import io.test.synthetic.Node; +import io.test.synthetic.v1.Syn1Contact; +import io.test.synthetic.v1.Syn1Document; +import io.test.synthetic.v1.Syn1Info; +import io.test.synthetic.v1.Syn1Item; +import io.test.synthetic.v1.Syn1Operation; +import io.test.synthetic.v1.Syn1PathItem; +import io.test.synthetic.v1.Syn1Paths; +import io.test.synthetic.v1.Syn1Schema; +import io.test.synthetic.visitors.diff.AbstractDiffTraverser; +import io.test.synthetic.visitors.diff.DiffVisitor; + +public class Syn1DiffTraverser extends AbstractDiffTraverser { + + public Syn1DiffTraverser(DiffVisitor visitor) { + super(visitor); + } + + @Override + protected void traverseNode(Node original, Node updated) { + Node target = original != null ? original : updated; + if (target instanceof Syn1Document) { + this.traverseDocument((Syn1Document) original, (Syn1Document) updated); + } else if (target instanceof Syn1Info) { + this.traverseInfo((Syn1Info) original, (Syn1Info) updated); + } else if (target instanceof Syn1Contact) { + this.traverseContact((Syn1Contact) original, (Syn1Contact) updated); + } else if (target instanceof Syn1Item) { + this.traverseItem((Syn1Item) original, (Syn1Item) updated); + } else if (target instanceof Syn1Schema) { + this.traverseSchema((Syn1Schema) original, (Syn1Schema) updated); + } else if (target instanceof Syn1Paths) { + this.traversePaths((Syn1Paths) original, (Syn1Paths) updated); + } else if (target instanceof Syn1PathItem) { + this.traversePathItem((Syn1PathItem) original, (Syn1PathItem) updated); + } else if (target instanceof Syn1Operation) { + this.traverseOperation((Syn1Operation) original, (Syn1Operation) updated); + } + } + + public void traverseDocument(Syn1Document original, Syn1Document updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("version", original.getVersion(), updated.getVersion()); + this.diffEntityField("info", original.getInfo(), updated.getInfo()); + this.diffList("items", original.getItems(), updated.getItems()); + visitor.diffPrimitive("tags", original.getTags(), updated.getTags()); + visitor.diffPrimitive("metadata", original.getMetadata(), updated.getMetadata()); + this.diffUnionField("additionalSchema", original.getAdditionalSchema(), updated.getAdditionalSchema()); + } + + public void traverseInfo(Syn1Info original, Syn1Info updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("name", original.getName(), updated.getName()); + this.diffEntityField("contact", original.getContact(), updated.getContact()); + visitor.diffPrimitive("version", original.getVersion(), updated.getVersion()); + } + + public void traverseContact(Syn1Contact original, Syn1Contact updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("name", original.getName(), updated.getName()); + visitor.diffPrimitive("email", original.getEmail(), updated.getEmail()); + visitor.diffPrimitive("url", original.getUrl(), updated.getUrl()); + } + + public void traverseItem(Syn1Item original, Syn1Item updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); + visitor.diffPrimitive("description", original.getDescription(), updated.getDescription()); + visitor.diffPrimitive("required", original.isRequired(), updated.isRequired()); + visitor.diffPrimitive("order", original.getOrder(), updated.getOrder()); + visitor.diffPrimitive("weight", original.getWeight(), updated.getWeight()); + visitor.diffPrimitive("extra", original.getExtra(), updated.getExtra()); + visitor.diffPrimitive("raw", original.getRaw(), updated.getRaw()); + this.diffEntityField("schema", original.getSchema(), updated.getSchema()); + visitor.diffPrimitive("examples", original.getExamples(), updated.getExamples()); + this.diffUnionField("defaultValue", original.getDefaultValue(), updated.getDefaultValue()); + visitor.diffPrimitive("title", original.getTitle(), updated.getTitle()); + } + + public void traverseSchema(Syn1Schema original, Syn1Schema updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); + visitor.diffPrimitive("type", original.getType(), updated.getType()); + this.diffUnionField("items", original.getItems(), updated.getItems()); + this.diffMap("properties", original.getProperties(), updated.getProperties()); + this.diffList("allOf", original.getAllOf(), updated.getAllOf()); + this.diffMap("definitions", original.getDefinitions(), updated.getDefinitions()); + this.diffMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); + this.diffList("composedSchemas", original.getComposedSchemas(), updated.getComposedSchemas()); + visitor.diffPrimitive("minLength", original.getMinLength(), updated.getMinLength()); + visitor.diffPrimitive("maxLength", original.getMaxLength(), updated.getMaxLength()); + visitor.diffPrimitive("enum", original.getEnum(), updated.getEnum()); + } + + public void traversePaths(Syn1Paths original, Syn1Paths updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + } + + public void traversePathItem(Syn1PathItem original, Syn1PathItem updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); + visitor.diffPrimitive("summary", original.getSummary(), updated.getSummary()); + this.diffEntityField("get", original.getGet(), updated.getGet()); + this.diffEntityField("put", original.getPut(), updated.getPut()); + this.diffEntityField("post", original.getPost(), updated.getPost()); + } + + public void traverseOperation(Syn1Operation original, Syn1Operation updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("operationId", original.getOperationId(), updated.getOperationId()); + visitor.diffPrimitive("summary", original.getSummary(), updated.getSummary()); + visitor.diffPrimitive("tags", original.getTags(), updated.getTags()); + this.diffList("parameters", original.getParameters(), updated.getParameters()); + } +} \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java new file mode 100644 index 000000000..76ab720b1 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -0,0 +1,161 @@ +package io.test.synthetic.v2.visitors; + +import io.test.synthetic.Node; +import io.test.synthetic.v2.Syn2Contact; +import io.test.synthetic.v2.Syn2Document; +import io.test.synthetic.v2.Syn2Info; +import io.test.synthetic.v2.Syn2Item; +import io.test.synthetic.v2.Syn2Operation; +import io.test.synthetic.v2.Syn2PathItem; +import io.test.synthetic.v2.Syn2Paths; +import io.test.synthetic.v2.Syn2Schema; +import io.test.synthetic.visitors.diff.AbstractDiffTraverser; +import io.test.synthetic.visitors.diff.DiffVisitor; + +public class Syn2DiffTraverser extends AbstractDiffTraverser { + + public Syn2DiffTraverser(DiffVisitor visitor) { + super(visitor); + } + + @Override + protected void traverseNode(Node original, Node updated) { + Node target = original != null ? original : updated; + if (target instanceof Syn2Document) { + this.traverseDocument((Syn2Document) original, (Syn2Document) updated); + } else if (target instanceof Syn2Info) { + this.traverseInfo((Syn2Info) original, (Syn2Info) updated); + } else if (target instanceof Syn2Contact) { + this.traverseContact((Syn2Contact) original, (Syn2Contact) updated); + } else if (target instanceof Syn2Item) { + this.traverseItem((Syn2Item) original, (Syn2Item) updated); + } else if (target instanceof Syn2Schema) { + this.traverseSchema((Syn2Schema) original, (Syn2Schema) updated); + } else if (target instanceof Syn2Paths) { + this.traversePaths((Syn2Paths) original, (Syn2Paths) updated); + } else if (target instanceof Syn2PathItem) { + this.traversePathItem((Syn2PathItem) original, (Syn2PathItem) updated); + } else if (target instanceof Syn2Operation) { + this.traverseOperation((Syn2Operation) original, (Syn2Operation) updated); + } + } + + public void traverseDocument(Syn2Document original, Syn2Document updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("version", original.getVersion(), updated.getVersion()); + this.diffEntityField("info", original.getInfo(), updated.getInfo()); + this.diffList("items", original.getItems(), updated.getItems()); + visitor.diffPrimitive("tags", original.getTags(), updated.getTags()); + visitor.diffPrimitive("metadata", original.getMetadata(), updated.getMetadata()); + this.diffMap("webhooks", original.getWebhooks(), updated.getWebhooks()); + this.diffUnionField("additionalSchema", original.getAdditionalSchema(), updated.getAdditionalSchema()); + } + + public void traverseInfo(Syn2Info original, Syn2Info updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("name", original.getName(), updated.getName()); + this.diffEntityField("contact", original.getContact(), updated.getContact()); + visitor.diffPrimitive("version", original.getVersion(), updated.getVersion()); + visitor.diffPrimitive("license", original.getLicense(), updated.getLicense()); + } + + public void traverseContact(Syn2Contact original, Syn2Contact updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("name", original.getName(), updated.getName()); + visitor.diffPrimitive("email", original.getEmail(), updated.getEmail()); + visitor.diffPrimitive("url", original.getUrl(), updated.getUrl()); + } + + public void traverseItem(Syn2Item original, Syn2Item updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); + visitor.diffPrimitive("description", original.getDescription(), updated.getDescription()); + visitor.diffPrimitive("required", original.isRequired(), updated.isRequired()); + visitor.diffPrimitive("order", original.getOrder(), updated.getOrder()); + visitor.diffPrimitive("weight", original.getWeight(), updated.getWeight()); + visitor.diffPrimitive("extra", original.getExtra(), updated.getExtra()); + visitor.diffPrimitive("raw", original.getRaw(), updated.getRaw()); + this.diffEntityField("schema", original.getSchema(), updated.getSchema()); + visitor.diffPrimitive("examples", original.getExamples(), updated.getExamples()); + this.diffUnionField("defaultValue", original.getDefaultValue(), updated.getDefaultValue()); + visitor.diffPrimitive("title", original.getTitle(), updated.getTitle()); + visitor.diffPrimitive("deprecated", original.isDeprecated(), updated.isDeprecated()); + } + + public void traverseSchema(Syn2Schema original, Syn2Schema updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); + visitor.diffPrimitive("type", original.getType(), updated.getType()); + this.diffUnionField("items", original.getItems(), updated.getItems()); + this.diffMap("properties", original.getProperties(), updated.getProperties()); + this.diffList("allOf", original.getAllOf(), updated.getAllOf()); + this.diffMap("definitions", original.getDefinitions(), updated.getDefinitions()); + this.diffMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); + this.diffList("composedSchemas", original.getComposedSchemas(), updated.getComposedSchemas()); + visitor.diffPrimitive("minLength", original.getMinLength(), updated.getMinLength()); + visitor.diffPrimitive("maxLength", original.getMaxLength(), updated.getMaxLength()); + visitor.diffPrimitive("enum", original.getEnum(), updated.getEnum()); + } + + public void traversePaths(Syn2Paths original, Syn2Paths updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + } + + public void traversePathItem(Syn2PathItem original, Syn2PathItem updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); + visitor.diffPrimitive("summary", original.getSummary(), updated.getSummary()); + this.diffEntityField("get", original.getGet(), updated.getGet()); + this.diffEntityField("put", original.getPut(), updated.getPut()); + this.diffEntityField("post", original.getPost(), updated.getPost()); + this.diffEntityField("delete", original.getDelete(), updated.getDelete()); + } + + public void traverseOperation(Syn2Operation original, Syn2Operation updated) { + if (original == null && updated == null) + return; + if (!visitor.visitEntityPair(original, updated)) + return; + if (original == null || updated == null) + return; + visitor.diffPrimitive("operationId", original.getOperationId(), updated.getOperationId()); + visitor.diffPrimitive("summary", original.getSummary(), updated.getSummary()); + visitor.diffPrimitive("tags", original.getTags(), updated.getTags()); + this.diffList("parameters", original.getParameters(), updated.getParameters()); + } +} \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java new file mode 100644 index 000000000..7ef8fb998 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java @@ -0,0 +1,80 @@ +package io.test.synthetic.visitors.diff; + +import io.test.synthetic.Any; +import io.test.synthetic.Node; +import io.test.synthetic.union.Union; +import java.util.List; +import java.util.Map; + +/** + * Base class for generated diff traversers. Provides shared collection diffing + * logic. Subclasses (generated per spec version) provide entity-specific field + * iteration. + */ +public class AbstractDiffTraverser { + + protected final DiffVisitor visitor; + + protected AbstractDiffTraverser(DiffVisitor visitor) { + this.visitor = visitor; + } + + @SuppressWarnings("unchecked") + protected void diffMap(String propertyName, Map original, Map updated) { + PairingStrategy strategy = (PairingStrategy) visitor.getPairingStrategy(propertyName); + if (strategy == null) { + strategy = new KeyPairingStrategy<>(); + } + + CollectionDiff diff = strategy.pair(original != null ? original : Map.of(), + updated != null ? updated : Map.of()); + visitor.diffMap(propertyName, diff); + + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + recurseIntoPair(pair.getOriginal(), pair.getUpdated()); + } + } + + @SuppressWarnings("unchecked") + protected void diffList(String propertyName, List original, List updated) { + PairingStrategy strategy = (PairingStrategy) visitor.getPairingStrategy(propertyName); + if (strategy == null) { + strategy = new IndexPairingStrategy<>(); + } + + CollectionDiff diff = strategy.pair(IndexPairingStrategy.toIndexMap(original), + IndexPairingStrategy.toIndexMap(updated)); + visitor.diffList(propertyName, diff); + + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + recurseIntoPair(pair.getOriginal(), pair.getUpdated()); + } + } + + protected void diffEntityField(String propertyName, Node original, Node updated) { + visitor.diffEntity(propertyName, original, updated); + if (original != null && updated != null) { + traverseNode(original, updated); + } + } + + protected void diffUnionField(String propertyName, Union original, Union updated) { + visitor.diffUnion(propertyName, original, updated); + } + + private void recurseIntoPair(Object original, Object updated) { + if (original instanceof Node && updated instanceof Node) { + traverseNode((Node) original, (Node) updated); + } else if (original instanceof Union && updated instanceof Union) { + // Union pairs in collections — visitor handles dispatch + visitor.diffUnion(null, (Union) original, (Union) updated); + } + } + + /** + * Dispatches to the correct entity-specific traverse method. Generated + * subclasses override this with type-based dispatch. + */ + protected void traverseNode(Node original, Node updated) { + } +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/CollectionDiff.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/CollectionDiff.java new file mode 100644 index 000000000..36640b45d --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/CollectionDiff.java @@ -0,0 +1,81 @@ +package io.test.synthetic.visitors.diff; + +import java.util.ArrayList; +import java.util.List; + +/** + * Result of pairing two collections. Contains entries that were added, removed, + * or matched between the original and updated collections. + * + * @param + * the key type (String for maps, Integer for lists) + * @param + * the value type + */ +public class CollectionDiff { + + private final List> added; + private final List> removed; + private final List> matched; + + public CollectionDiff(List> added, List> removed, List> matched) { + this.added = added; + this.removed = removed; + this.matched = matched; + } + + public List> getAdded() { + return added; + } + + public List> getRemoved() { + return removed; + } + + public List> getMatched() { + return matched; + } + + public boolean hasChanges() { + return !added.isEmpty() || !removed.isEmpty(); + } + + public static class Entry { + private final K key; + private final V value; + + public Entry(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + public V getValue() { + return value; + } + } + + public static class MatchedPair { + private final K key; + private final V original; + private final V updated; + + public MatchedPair(K key, V original, V updated) { + this.key = key; + this.original = original; + this.updated = updated; + } + + public K getKey() { + return key; + } + public V getOriginal() { + return original; + } + public V getUpdated() { + return updated; + } + } +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java new file mode 100644 index 000000000..325a4661f --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java @@ -0,0 +1,65 @@ +package io.test.synthetic.visitors.diff; + +import io.test.synthetic.Node; +import io.test.synthetic.union.Union; + +/** + * Visitor for paired tree traversal. The generated DiffTraverser walks two + * trees in parallel, calling these methods for each field. Subclasses override + * the methods they care about. + * + *

+ * The traverser auto-recurses into entity fields and matched collection pairs. + * Override {@link #visitEntityPair} to skip recursion for specific entities. + */ +public abstract class DiffVisitor { + + /** + * Called before traversing fields of an entity pair. Return false to skip + * field-level traversal of this pair. + */ + public boolean visitEntityPair(Node original, Node updated) { + return true; + } + + /** + * Called for each primitive-typed field (String, Boolean, Number, etc.). + */ + public void diffPrimitive(String propertyName, Object original, Object updated) { + } + + /** + * Called for each entity-typed field. The traverser auto-recurses into both + * entities after this call (if both are non-null). + */ + public void diffEntity(String propertyName, Node original, Node updated) { + } + + /** + * Called for each union-typed field. + */ + public void diffUnion(String propertyName, Union original, Union updated) { + } + + /** + * Called for each list-typed field with the pairing result. The traverser + * auto-recurses into matched entity/union pairs after this call. + */ + public void diffList(String propertyName, CollectionDiff diff) { + } + + /** + * Called for each map-typed field with the pairing result. The traverser + * auto-recurses into matched entity/union pairs after this call. + */ + public void diffMap(String propertyName, CollectionDiff diff) { + } + + /** + * Returns the pairing strategy for a collection field, or null to use the + * default (key-based for maps, index-based for lists). + */ + public PairingStrategy getPairingStrategy(String propertyName) { + return null; + } +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java new file mode 100644 index 000000000..f6dd79945 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java @@ -0,0 +1,52 @@ +package io.test.synthetic.visitors.diff; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Pairs list entries by index. Items at the same index are paired; extra items + * in the longer list are added/removed. + */ +public class IndexPairingStrategy implements PairingStrategy { + + @Override + public CollectionDiff pair(Map original, Map updated) { + Map orig = original != null ? original : new LinkedHashMap<>(); + Map upd = updated != null ? updated : new LinkedHashMap<>(); + + int maxIndex = Math.max(orig.size(), upd.size()); + + List> added = new ArrayList<>(); + List> removed = new ArrayList<>(); + List> matched = new ArrayList<>(); + + for (int i = 0; i < maxIndex; i++) { + boolean inOrig = orig.containsKey(i); + boolean inUpd = upd.containsKey(i); + if (inOrig && inUpd) { + matched.add(new CollectionDiff.MatchedPair<>(i, orig.get(i), upd.get(i))); + } else if (inUpd) { + added.add(new CollectionDiff.Entry<>(i, upd.get(i))); + } else { + removed.add(new CollectionDiff.Entry<>(i, orig.get(i))); + } + } + + return new CollectionDiff<>(added, removed, matched); + } + + /** + * Converts a list to an index-keyed map for use with this strategy. + */ + public static Map toIndexMap(List list) { + if (list == null) + return new LinkedHashMap<>(); + Map map = new LinkedHashMap<>(); + for (int i = 0; i < list.size(); i++) { + map.put(i, list.get(i)); + } + return map; + } +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java new file mode 100644 index 000000000..43b04aaa9 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java @@ -0,0 +1,43 @@ +package io.test.synthetic.visitors.diff; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Pairs map entries by key. Entries with matching keys are paired; keys present + * in only one map are added/removed. + */ +public class KeyPairingStrategy implements PairingStrategy { + + @Override + public CollectionDiff pair(Map original, Map updated) { + Map orig = original != null ? original : new LinkedHashMap<>(); + Map upd = updated != null ? updated : new LinkedHashMap<>(); + + Set allKeys = new LinkedHashSet<>(); + allKeys.addAll(orig.keySet()); + allKeys.addAll(upd.keySet()); + + List> added = new ArrayList<>(); + List> removed = new ArrayList<>(); + List> matched = new ArrayList<>(); + + for (String key : allKeys) { + boolean inOrig = orig.containsKey(key); + boolean inUpd = upd.containsKey(key); + if (inOrig && inUpd) { + matched.add(new CollectionDiff.MatchedPair<>(key, orig.get(key), upd.get(key))); + } else if (inUpd) { + added.add(new CollectionDiff.Entry<>(key, upd.get(key))); + } else { + removed.add(new CollectionDiff.Entry<>(key, orig.get(key))); + } + } + + return new CollectionDiff<>(added, removed, matched); + } +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java new file mode 100644 index 000000000..8929be83c --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java @@ -0,0 +1,15 @@ +package io.test.synthetic.visitors.diff; + +/** + * Strategy for pairing elements from two collections to produce a + * {@link CollectionDiff}. + * + * @param + * the key type (String for maps, Integer for lists) + * @param + * the value type + */ +public interface PairingStrategy { + + CollectionDiff pair(java.util.Map original, java.util.Map updated); +} From 0e6fdf0e8609e813dc4c19c1ef690614b576f643 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 14:21:35 +0200 Subject: [PATCH 03/16] feat(generator): typed per-field DiffVisitor, consolidate pairing strategies (C36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generated DiffVisitor per spec version with typed, field-specific methods: - Entity-level: visitFullSchema(original, updated) → boolean - Primitive: diffFullSchemaTitle(String, String), diffFullSchemaMinLength(Number, Number) - Entity/Union: diffFullSchemaNot(JsonSchema, JsonSchema) - Map: diffFullSchemaProperties(CollectionDiff) + visitFullSchemaProperty(String key, JsonSchema, JsonSchema) - List: diffFullSchemaAllOf(CollectionDiff) + visitFullSchemaAllOfItem(Integer index, JsonSchema, JsonSchema) Method names prefixed with entity name to avoid collisions across entity types. DiffTraverser now generic (AbstractDiffTraverser), calls typed visitor methods instead of generic diffPrimitive/diffMap/etc. Extracted shared pairing logic into PairingStrategy.pairByKey() static method. KeyPairingStrategy and IndexPairingStrategy both delegate to it. --- .../io/apitomy/umg/UnifiedModelGenerator.java | 2 + .../pipe/java/CreateDiffTraversersStage.java | 120 +++++++-- .../pipe/java/CreateDiffVisitorsStage.java | 230 ++++++++++++++++++ .../visitors/diff/AbstractDiffTraverser.java | 61 ++--- .../umg/base/visitors/diff/DiffVisitor.java | 49 +--- .../visitors/diff/IndexPairingStrategy.java | 28 +-- .../visitors/diff/KeyPairingStrategy.java | 33 +-- .../base/visitors/diff/PairingStrategy.java | 36 ++- .../io/apitomy/umg/full/expected-manifest.txt | 19 ++ .../v1/visitors/Syn1DiffTraverser.java | 192 +++++++++++---- .../v1/visitors/Syn1DiffVisitor.java | 209 ++++++++++++++++ .../v2/visitors/Syn2DiffTraverser.java | 213 +++++++++++----- .../v2/visitors/Syn2DiffVisitor.java | 224 +++++++++++++++++ .../visitors/diff/AbstractDiffTraverser.java | 60 ++--- .../synthetic/visitors/diff/DiffVisitor.java | 50 +--- .../visitors/diff/IndexPairingStrategy.java | 28 +-- .../visitors/diff/KeyPairingStrategy.java | 33 +-- .../visitors/diff/PairingStrategy.java | 36 ++- 18 files changed, 1190 insertions(+), 433 deletions(-) create mode 100644 generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java diff --git a/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java b/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java index 3b50c94e0..6caf78b75 100644 --- a/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java +++ b/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java @@ -59,6 +59,7 @@ import io.apitomy.umg.pipe.java.CreateTestFixturesStage; import io.apitomy.umg.pipe.java.CreateTraitInterfacesStage; import io.apitomy.umg.pipe.java.CreateDiffTraversersStage; +import io.apitomy.umg.pipe.java.CreateDiffVisitorsStage; import io.apitomy.umg.pipe.java.CreateTraversersStage; import io.apitomy.umg.pipe.java.CreateCollectionUnionValuesStage; import io.apitomy.umg.pipe.java.CreatePrimitiveUnionValuesStage; @@ -164,6 +165,7 @@ public void generate() throws Exception { pipe.addStage(new CreateWriterDispatchersStage()); pipe.addStage(new CreateClonerDispatchersStage()); pipe.addStage(new CreateTraversersStage()); + pipe.addStage(new CreateDiffVisitorsStage()); pipe.addStage(new CreateDiffTraversersStage()); pipe.addStage(new CreateReaderFactoryStage()); pipe.addStage(new CreateWriterFactoryStage()); diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index 316986ea9..7588458fa 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -1,8 +1,8 @@ package io.apitomy.umg.pipe.java; import java.util.Collection; -import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.JavaInterfaceSource; @@ -11,14 +11,19 @@ import io.apitomy.umg.beans.SpecificationVersion; import io.apitomy.umg.models.concept.EntityModel; import io.apitomy.umg.models.concept.PropertyModel; +import io.apitomy.umg.models.concept.NamespaceModel; import io.apitomy.umg.models.concept.PropertyModelWithOrigin; +import io.apitomy.umg.models.concept.type.ListType; +import io.apitomy.umg.models.concept.type.MapType; +import io.apitomy.umg.models.java.type.JavaType; +import io.apitomy.umg.models.java.type.JavaTypeFactory; import io.apitomy.umg.pipe.java.method.BodyBuilder; import io.apitomy.umg.pipe.java.method.GetterMethod; /** * Creates a DiffTraverser for each specification version. A DiffTraverser walks two - * trees of the same spec version in parallel, dispatching field-level diffs to a - * DiffVisitor. + * trees of the same spec version in parallel, dispatching field-level diffs to the + * generated per-spec DiffVisitor's typed methods. */ public class CreateDiffTraversersStage extends AbstractJavaStage { @@ -38,24 +43,32 @@ private void createDiffTraverser(SpecificationVersion specVer) { .setName(className) .setPublic(); + // Resolve the generated per-spec visitor + String visitorClassName = specVer.getPrefix() + "DiffVisitor"; + String visitorFQN = packageName + "." + visitorClassName; + JavaClassSource visitorSource = getState().getJavaIndex().lookupClass(visitorFQN); + classSource.addImport(visitorSource); + + // Extend AbstractDiffTraverser String baseFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.AbstractDiffTraverser"; JavaClassSource baseSource = getState().getJavaIndex().lookupClass(baseFQN); classSource.addImport(baseSource); - classSource.extendSuperType(baseSource); - - String diffVisitorFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.DiffVisitor"; - JavaClassSource diffVisitorSource = getState().getJavaIndex().lookupClass(diffVisitorFQN); - classSource.addImport(diffVisitorSource); + classSource.setSuperType("AbstractDiffTraverser<" + visitorClassName + ">"); - MethodSource constructor = classSource.addMethod() - .setConstructor(true).setPublic(); - constructor.addParameter("DiffVisitor", "visitor"); - constructor.setBody("super(visitor);"); + // Import CollectionDiff for collection field handling + String collectionDiffFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.CollectionDiff"; + classSource.addImport(collectionDiffFQN); String nodeFQN = getNodeEntityInterfaceFQN(); JavaInterfaceSource nodeSource = getState().getJavaIndex().lookupInterface(nodeFQN); classSource.addImport(nodeSource); + // Constructor + MethodSource constructor = classSource.addMethod() + .setConstructor(true).setPublic(); + constructor.addParameter(visitorClassName, "visitor"); + constructor.setBody("super(visitor);"); + java.util.Set createdMethods = new java.util.HashSet<>(); createTraverseNodeDispatch(specVer, classSource, createdMethods); @@ -123,7 +136,8 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, classSource.addImport(javaEntity); String entityType = javaEntity.getName(); - String methodName = "traverse" + entityModel.getName(); + String entityName = entityModel.getName(); + String methodName = "traverse" + entityName; MethodSource method = classSource.addMethod() .setName(methodName) @@ -135,10 +149,12 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, Collection allProperties = getState().getConceptIndex().getAllEntityProperties(entityModel); + JavaTypeFactory jtf = getJavaTypeFactory(); BodyBuilder body = new BodyBuilder(); body.append("if (original == null && updated == null) return;"); - body.append("if (!visitor.visitEntityPair(original, updated)) return;"); + body.addContext("visitMethod", "visit" + entityName); + body.append("if (!visitor.${visitMethod}(original, updated)) return;"); body.append("if (original == null || updated == null) return;"); for (PropertyModelWithOrigin propWithOrigin : allProperties) { @@ -148,22 +164,86 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, } String getter = new GetterMethod(property).getName(); - body.addContext("propertyName", property.getName()); + String fieldSuffix = fieldMethodSuffix(property); + String diffMethod = "diff" + entityName + fieldSuffix; + body.addContext("getter", getter); + body.addContext("diffMethod", diffMethod); + + NamespaceModel ns = propWithOrigin.getOrigin().getNamespace(); if (isEntity(property)) { - body.append("this.diffEntityField(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + JavaType jt = jtf.createJavaType(property.getResolvedType(), ns); + jt.addImportsTo(classSource); + body.append("visitor.${diffMethod}(original.${getter}(), updated.${getter}());"); + body.append("if (original.${getter}() != null && updated.${getter}() != null) {"); + body.append(" traverseNode(original.${getter}(), updated.${getter}());"); + body.append("}"); } else if (isEntityList(property) || isUnionList(property)) { - body.append("this.diffList(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + ListType listType = (ListType) property.getResolvedType(); + JavaType valueJt = jtf.createJavaType(listType.getValueType(), ns); + valueJt.addImportsTo(classSource); + + String valueTypeStr = valueJt.toJavaTypeString(); + String visitMethod = "visit" + entityName + fieldSuffix + "Item"; + body.addContext("valueType", valueTypeStr); + body.addContext("visitMethod2", visitMethod); + body.addContext("propertyName", property.getName()); + + body.append("{"); + body.append(" CollectionDiff diff = this.pairList(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + body.append(" visitor.${diffMethod}(diff);"); + body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); + body.append(" visitor.${visitMethod2}(pair.getKey(), pair.getOriginal(), pair.getUpdated());"); + if (isEntityList(property)) { + body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); + body.append(" traverseNode(pair.getOriginal(), pair.getUpdated());"); + body.append(" }"); + } + body.append(" }"); + body.append("}"); } else if (isEntityMap(property) || isUnionMap(property)) { - body.append("this.diffMap(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + MapType mapType = (MapType) property.getResolvedType(); + JavaType valueJt = jtf.createJavaType(mapType.getValueType(), ns); + valueJt.addImportsTo(classSource); + + String valueTypeStr = valueJt.toJavaTypeString(); + String visitMethod = "visit" + entityName + fieldSuffix; + body.addContext("valueType", valueTypeStr); + body.addContext("visitMethod2", visitMethod); + body.addContext("propertyName", property.getName()); + + body.append("{"); + body.append(" CollectionDiff diff = this.pairMap(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + body.append(" visitor.${diffMethod}(diff);"); + body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); + body.append(" visitor.${visitMethod2}(pair.getKey(), pair.getOriginal(), pair.getUpdated());"); + if (isEntityMap(property)) { + body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); + body.append(" traverseNode(pair.getOriginal(), pair.getUpdated());"); + body.append(" }"); + } + body.append(" }"); + body.append("}"); } else if (isUnion(property)) { - body.append("this.diffUnionField(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + JavaType jt = jtf.createJavaType(property.getResolvedType(), ns); + jt.addImportsTo(classSource); + body.append("visitor.${diffMethod}(original.${getter}(), updated.${getter}());"); } else if (isPrimitive(property) || isPrimitiveList(property) || isPrimitiveMap(property)) { - body.append("visitor.diffPrimitive(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + JavaType jt = jtf.createJavaType(property.getResolvedType(), ns); + jt.addImportsTo(classSource); + body.append("visitor.${diffMethod}(original.${getter}(), updated.${getter}());"); } } method.setBody(body.toString()); } + + private String fieldMethodSuffix(PropertyModel property) { + String name = property.getName(); + if (name.startsWith("$")) { + return name; + } + return StringUtils.capitalize(name); + } } diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java new file mode 100644 index 000000000..74eb8ddd3 --- /dev/null +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java @@ -0,0 +1,230 @@ +package io.apitomy.umg.pipe.java; + +import java.util.Collection; + +import org.apache.commons.lang3.StringUtils; +import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.MethodSource; + +import io.apitomy.umg.beans.SpecificationVersion; +import io.apitomy.umg.models.concept.EntityModel; +import io.apitomy.umg.models.concept.PropertyModel; +import io.apitomy.umg.models.concept.NamespaceModel; +import io.apitomy.umg.models.concept.PropertyModelWithOrigin; +import io.apitomy.umg.models.concept.type.ListType; +import io.apitomy.umg.models.concept.type.MapType; +import io.apitomy.umg.models.java.type.JavaType; +import io.apitomy.umg.models.java.type.JavaTypeFactory; + +/** + * Creates a DiffVisitor abstract class per spec version. Each generated visitor + * contains typed, field-specific methods for every entity and property in the spec. + */ +public class CreateDiffVisitorsStage extends AbstractJavaStage { + + @Override + protected void doProcess() { + getState().getSpecIndex().getAllSpecificationVersions().forEach(this::createDiffVisitor); + } + + private void createDiffVisitor(SpecificationVersion specVer) { + String packageName = getTraverserPackageName(specVer); + String className = specVer.getPrefix() + "DiffVisitor"; + + debug("Creating diff visitor: " + className); + + JavaClassSource classSource = Roaster.create(JavaClassSource.class) + .setPackage(packageName) + .setName(className) + .setPublic() + .setAbstract(true); + + String baseFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.DiffVisitor"; + JavaClassSource baseSource = getState().getJavaIndex().lookupClass(baseFQN); + classSource.addImport(baseSource); + classSource.extendSuperType(baseSource); + + JavaTypeFactory jtf = getJavaTypeFactory(); + + specVer.getEntities().forEach(entity -> { + EntityModel entityModel = getState().getConceptIndex() + .lookupEntity(specVer.getNamespace() + "." + entity.getName()); + if (entityModel == null) { + return; + } + + createEntityVisitMethod(classSource, entityModel); + createPropertyMethods(classSource, entityModel, jtf); + }); + + getState().getJavaIndex().index(classSource); + } + + private void createEntityVisitMethod(JavaClassSource classSource, EntityModel entityModel) { + var javaEntity = lookupJavaEntity(entityModel); + classSource.addImport(javaEntity); + + String methodName = "visit" + entityModel.getName(); + MethodSource method = classSource.addMethod() + .setName(methodName) + .setReturnType(boolean.class) + .setPublic(); + method.addParameter(javaEntity.getName(), "original"); + method.addParameter(javaEntity.getName(), "updated"); + method.setBody("return true;"); + } + + private void createPropertyMethods(JavaClassSource classSource, EntityModel entityModel, + JavaTypeFactory jtf) { + Collection allProperties = + getState().getConceptIndex().getAllEntityProperties(entityModel); + + for (PropertyModelWithOrigin propWithOrigin : allProperties) { + PropertyModel property = propWithOrigin.getProperty(); + if (isStarProperty(property) || isRegexProperty(property)) { + continue; + } + + String entityName = entityModel.getName(); + String fieldSuffix = fieldMethodSuffix(property); + NamespaceModel ns = propWithOrigin.getOrigin().getNamespace(); + + if (isEntity(property)) { + createEntityFieldMethod(classSource, entityName, fieldSuffix, property, ns, jtf); + } else if (isEntityList(property)) { + createListFieldMethods(classSource, entityName, fieldSuffix, property, ns, jtf); + } else if (isEntityMap(property)) { + createMapFieldMethods(classSource, entityName, fieldSuffix, property, ns, jtf); + } else if (isUnion(property)) { + createUnionFieldMethod(classSource, entityName, fieldSuffix, property, ns, jtf); + } else if (isUnionList(property)) { + createListFieldMethods(classSource, entityName, fieldSuffix, property, ns, jtf); + } else if (isUnionMap(property)) { + createMapFieldMethods(classSource, entityName, fieldSuffix, property, ns, jtf); + } else if (isPrimitive(property) || isPrimitiveList(property) || isPrimitiveMap(property)) { + createPrimitiveFieldMethod(classSource, entityName, fieldSuffix, property, ns, jtf); + } + } + } + + private void createPrimitiveFieldMethod(JavaClassSource classSource, String entityName, + String fieldSuffix, PropertyModel property, NamespaceModel ns, JavaTypeFactory jtf) { + JavaType jt = jtf.createJavaType(property.getResolvedType(), ns); + jt.addImportsTo(classSource); + + String methodName = "diff" + entityName + fieldSuffix; + MethodSource method = classSource.addMethod() + .setName(methodName) + .setReturnTypeVoid() + .setPublic(); + method.addParameter(jt.toJavaTypeString(), "original"); + method.addParameter(jt.toJavaTypeString(), "updated"); + method.setBody(""); + } + + private void createEntityFieldMethod(JavaClassSource classSource, String entityName, + String fieldSuffix, PropertyModel property, NamespaceModel ns, JavaTypeFactory jtf) { + JavaType jt = jtf.createJavaType(property.getResolvedType(), ns); + jt.addImportsTo(classSource); + + String methodName = "diff" + entityName + fieldSuffix; + MethodSource method = classSource.addMethod() + .setName(methodName) + .setReturnTypeVoid() + .setPublic(); + method.addParameter(jt.toJavaTypeString(), "original"); + method.addParameter(jt.toJavaTypeString(), "updated"); + method.setBody(""); + } + + private void createUnionFieldMethod(JavaClassSource classSource, String entityName, + String fieldSuffix, PropertyModel property, NamespaceModel ns, JavaTypeFactory jtf) { + JavaType jt = jtf.createJavaType(property.getResolvedType(), ns); + jt.addImportsTo(classSource); + + String methodName = "diff" + entityName + fieldSuffix; + MethodSource method = classSource.addMethod() + .setName(methodName) + .setReturnTypeVoid() + .setPublic(); + method.addParameter(jt.toJavaTypeString(), "original"); + method.addParameter(jt.toJavaTypeString(), "updated"); + method.setBody(""); + } + + private void createListFieldMethods(JavaClassSource classSource, String entityName, + String fieldSuffix, PropertyModel property, NamespaceModel ns, JavaTypeFactory jtf) { + ListType listType = (ListType) property.getResolvedType(); + JavaType valueJt = jtf.createJavaType(listType.getValueType(), ns); + valueJt.addImportsTo(classSource); + + String collectionDiffFQN = getState().getConfig().getRootNamespace() + + ".visitors.diff.CollectionDiff"; + classSource.addImport(collectionDiffFQN); + + // diff method: diffEntityField(CollectionDiff diff) + String diffMethodName = "diff" + entityName + fieldSuffix; + MethodSource diffMethod = classSource.addMethod() + .setName(diffMethodName) + .setReturnTypeVoid() + .setPublic(); + diffMethod.addParameter("CollectionDiff", "diff"); + diffMethod.setBody(""); + + // visit method: visitEntityFieldItem(Integer index, ValueType original, ValueType updated) + String visitMethodName = "visit" + entityName + fieldSuffix + "Item"; + MethodSource visitMethod = classSource.addMethod() + .setName(visitMethodName) + .setReturnTypeVoid() + .setPublic(); + visitMethod.addParameter("Integer", "index"); + visitMethod.addParameter(valueJt.toJavaTypeString(), "original"); + visitMethod.addParameter(valueJt.toJavaTypeString(), "updated"); + visitMethod.setBody(""); + } + + private void createMapFieldMethods(JavaClassSource classSource, String entityName, + String fieldSuffix, PropertyModel property, NamespaceModel ns, JavaTypeFactory jtf) { + MapType mapType = (MapType) property.getResolvedType(); + JavaType valueJt = jtf.createJavaType(mapType.getValueType(), ns); + valueJt.addImportsTo(classSource); + + String collectionDiffFQN = getState().getConfig().getRootNamespace() + + ".visitors.diff.CollectionDiff"; + classSource.addImport(collectionDiffFQN); + + // diff method: diffEntityField(CollectionDiff diff) + String diffMethodName = "diff" + entityName + fieldSuffix; + MethodSource diffMethod = classSource.addMethod() + .setName(diffMethodName) + .setReturnTypeVoid() + .setPublic(); + diffMethod.addParameter("CollectionDiff", "diff"); + diffMethod.setBody(""); + + // visit method: visitEntityField(String key, ValueType original, ValueType updated) + String singularFieldSuffix = fieldSuffix; + String visitMethodName = "visit" + entityName + singularFieldSuffix; + MethodSource visitMethod = classSource.addMethod() + .setName(visitMethodName) + .setReturnTypeVoid() + .setPublic(); + visitMethod.addParameter("String", "key"); + visitMethod.addParameter(valueJt.toJavaTypeString(), "original"); + visitMethod.addParameter(valueJt.toJavaTypeString(), "updated"); + visitMethod.setBody(""); + } + + /** + * Turns a property name into a method name suffix. + * "title" → "Title", "$ref" → "$ref", "allOf" → "AllOf" + */ + private String fieldMethodSuffix(PropertyModel property) { + String name = property.getName(); + if (name.startsWith("$")) { + return name; + } + return StringUtils.capitalize(name); + } +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java index 3a0551ec1..77ec62d84 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -3,76 +3,45 @@ import java.util.List; import java.util.Map; -import io.apitomy.umg.base.Any; import io.apitomy.umg.base.Node; -import io.apitomy.umg.base.union.Union; /** * Base class for generated diff traversers. Provides shared collection diffing logic. - * Subclasses (generated per spec version) provide entity-specific field iteration. + * Subclasses (generated per spec version) provide entity-specific field iteration + * and call typed visitor methods. + * + * @param the spec-version-specific DiffVisitor subclass */ -public class AbstractDiffTraverser { +public class AbstractDiffTraverser { - protected final DiffVisitor visitor; + protected final V visitor; - protected AbstractDiffTraverser(DiffVisitor visitor) { + protected AbstractDiffTraverser(V visitor) { this.visitor = visitor; } @SuppressWarnings("unchecked") - protected void diffMap(String propertyName, Map original, Map updated) { - PairingStrategy strategy = - (PairingStrategy) visitor.getPairingStrategy(propertyName); + protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { + PairingStrategy strategy = + (PairingStrategy) visitor.getPairingStrategy(propertyName); if (strategy == null) { strategy = new KeyPairingStrategy<>(); } - - CollectionDiff diff = strategy.pair( + return strategy.pair( original != null ? original : Map.of(), updated != null ? updated : Map.of()); - visitor.diffMap(propertyName, diff); - - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - recurseIntoPair(pair.getOriginal(), pair.getUpdated()); - } } @SuppressWarnings("unchecked") - protected void diffList(String propertyName, List original, List updated) { - PairingStrategy strategy = - (PairingStrategy) visitor.getPairingStrategy(propertyName); + protected CollectionDiff pairList(String propertyName, List original, List updated) { + PairingStrategy strategy = + (PairingStrategy) visitor.getPairingStrategy(propertyName); if (strategy == null) { strategy = new IndexPairingStrategy<>(); } - - CollectionDiff diff = strategy.pair( + return strategy.pair( IndexPairingStrategy.toIndexMap(original), IndexPairingStrategy.toIndexMap(updated)); - visitor.diffList(propertyName, diff); - - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - recurseIntoPair(pair.getOriginal(), pair.getUpdated()); - } - } - - protected void diffEntityField(String propertyName, Node original, Node updated) { - visitor.diffEntity(propertyName, original, updated); - if (original != null && updated != null) { - traverseNode(original, updated); - } - } - - protected void diffUnionField(String propertyName, Union original, Union updated) { - visitor.diffUnion(propertyName, original, updated); - } - - private void recurseIntoPair(Object original, Object updated) { - if (original instanceof Node && updated instanceof Node) { - traverseNode((Node) original, (Node) updated); - } else if (original instanceof Union && updated instanceof Union) { - // Union pairs in collections — visitor handles dispatch - visitor.diffUnion(null, (Union) original, (Union) updated); - } } /** diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java index b747a63f6..15066b3bf 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java @@ -1,59 +1,14 @@ package io.apitomy.umg.base.visitors.diff; -import io.apitomy.umg.base.Node; -import io.apitomy.umg.base.union.Union; /** - * Visitor for paired tree traversal. The generated DiffTraverser walks two trees - * in parallel, calling these methods for each field. Subclasses override the methods - * they care about. + * Base class for generated per-spec-version DiffVisitors. Generated subclasses + * add typed, field-specific methods for each entity and property. * *

The traverser auto-recurses into entity fields and matched collection pairs. - * Override {@link #visitEntityPair} to skip recursion for specific entities. */ public abstract class DiffVisitor { - /** - * Called before traversing fields of an entity pair. Return false to skip - * field-level traversal of this pair. - */ - public boolean visitEntityPair(Node original, Node updated) { - return true; - } - - /** - * Called for each primitive-typed field (String, Boolean, Number, etc.). - */ - public void diffPrimitive(String propertyName, Object original, Object updated) { - } - - /** - * Called for each entity-typed field. The traverser auto-recurses into both - * entities after this call (if both are non-null). - */ - public void diffEntity(String propertyName, Node original, Node updated) { - } - - /** - * Called for each union-typed field. - */ - public void diffUnion(String propertyName, Union original, Union updated) { - } - - /** - * Called for each list-typed field with the pairing result. - * The traverser auto-recurses into matched entity/union pairs after this call. - */ - public void diffList(String propertyName, CollectionDiff diff) { - } - - /** - * Called for each map-typed field with the pairing result. - * The traverser auto-recurses into matched entity/union pairs after this call. - */ - public void diffMap(String propertyName, CollectionDiff diff) { - } - /** * Returns the pairing strategy for a collection field, or null to use the default * (key-based for maps, index-based for lists). diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java index 26b528cca..4252aa9f8 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java @@ -1,40 +1,18 @@ package io.apitomy.umg.base.visitors.diff; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** - * Pairs list entries by index. Items at the same index are paired; - * extra items in the longer list are added/removed. + * Pairs list entries by index. Converts lists to index-keyed maps, + * then delegates to the shared pairing logic. */ public class IndexPairingStrategy implements PairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { - Map orig = original != null ? original : new LinkedHashMap<>(); - Map upd = updated != null ? updated : new LinkedHashMap<>(); - - int maxIndex = Math.max(orig.size(), upd.size()); - - List> added = new ArrayList<>(); - List> removed = new ArrayList<>(); - List> matched = new ArrayList<>(); - - for (int i = 0; i < maxIndex; i++) { - boolean inOrig = orig.containsKey(i); - boolean inUpd = upd.containsKey(i); - if (inOrig && inUpd) { - matched.add(new CollectionDiff.MatchedPair<>(i, orig.get(i), upd.get(i))); - } else if (inUpd) { - added.add(new CollectionDiff.Entry<>(i, upd.get(i))); - } else { - removed.add(new CollectionDiff.Entry<>(i, orig.get(i))); - } - } - - return new CollectionDiff<>(added, removed, matched); + return PairingStrategy.pairByKey(original, updated); } /** diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java index d695c994c..bf5d0b629 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java @@ -1,43 +1,14 @@ package io.apitomy.umg.base.visitors.diff; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; -import java.util.Set; /** - * Pairs map entries by key. Entries with matching keys are paired; - * keys present in only one map are added/removed. + * Pairs map entries by key. */ public class KeyPairingStrategy implements PairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { - Map orig = original != null ? original : new LinkedHashMap<>(); - Map upd = updated != null ? updated : new LinkedHashMap<>(); - - Set allKeys = new LinkedHashSet<>(); - allKeys.addAll(orig.keySet()); - allKeys.addAll(upd.keySet()); - - List> added = new ArrayList<>(); - List> removed = new ArrayList<>(); - List> matched = new ArrayList<>(); - - for (String key : allKeys) { - boolean inOrig = orig.containsKey(key); - boolean inUpd = upd.containsKey(key); - if (inOrig && inUpd) { - matched.add(new CollectionDiff.MatchedPair<>(key, orig.get(key), upd.get(key))); - } else if (inUpd) { - added.add(new CollectionDiff.Entry<>(key, upd.get(key))); - } else { - removed.add(new CollectionDiff.Entry<>(key, orig.get(key))); - } - } - - return new CollectionDiff<>(added, removed, matched); + return PairingStrategy.pairByKey(original, updated); } } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java index c8be11442..5d0fedebc 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java @@ -1,5 +1,12 @@ package io.apitomy.umg.base.visitors.diff; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Strategy for pairing elements from two collections to produce a {@link CollectionDiff}. * @@ -8,5 +15,32 @@ */ public interface PairingStrategy { - CollectionDiff pair(java.util.Map original, java.util.Map updated); + CollectionDiff pair(Map original, Map updated); + + static CollectionDiff pairByKey(Map original, Map updated) { + Map orig = original != null ? original : new LinkedHashMap<>(); + Map upd = updated != null ? updated : new LinkedHashMap<>(); + + Set allKeys = new LinkedHashSet<>(); + allKeys.addAll(orig.keySet()); + allKeys.addAll(upd.keySet()); + + List> added = new ArrayList<>(); + List> removed = new ArrayList<>(); + List> matched = new ArrayList<>(); + + for (K key : allKeys) { + boolean inOrig = orig.containsKey(key); + boolean inUpd = upd.containsKey(key); + if (inOrig && inUpd) { + matched.add(new CollectionDiff.MatchedPair<>(key, orig.get(key), upd.get(key))); + } else if (inUpd) { + added.add(new CollectionDiff.Entry<>(key, upd.get(key))); + } else { + removed.add(new CollectionDiff.Entry<>(key, orig.get(key))); + } + } + + return new CollectionDiff<>(added, removed, matched); + } } diff --git a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt index 141aefdde..6789dba77 100644 --- a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt +++ b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt @@ -179,6 +179,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v20/io/AsyncApi20ModelReaderDispatcher io/apitomy/datamodels/models/asyncapi/v2x/v20/io/AsyncApi20ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v20/io/AsyncApi20ModelWriterDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v20/visitors/AsyncApi20DiffTraverser.java +io/apitomy/datamodels/models/asyncapi/v2x/v20/visitors/AsyncApi20DiffVisitor.java io/apitomy/datamodels/models/asyncapi/v2x/v20/visitors/AsyncApi20Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v20/visitors/AsyncApi20Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v20/visitors/AsyncApi20VisitorAdapter.java @@ -252,6 +253,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v21/io/AsyncApi21ModelReaderDispatcher io/apitomy/datamodels/models/asyncapi/v2x/v21/io/AsyncApi21ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v21/io/AsyncApi21ModelWriterDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v21/visitors/AsyncApi21DiffTraverser.java +io/apitomy/datamodels/models/asyncapi/v2x/v21/visitors/AsyncApi21DiffVisitor.java io/apitomy/datamodels/models/asyncapi/v2x/v21/visitors/AsyncApi21Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v21/visitors/AsyncApi21Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v21/visitors/AsyncApi21VisitorAdapter.java @@ -327,6 +329,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v22/io/AsyncApi22ModelReaderDispatcher io/apitomy/datamodels/models/asyncapi/v2x/v22/io/AsyncApi22ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v22/io/AsyncApi22ModelWriterDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v22/visitors/AsyncApi22DiffTraverser.java +io/apitomy/datamodels/models/asyncapi/v2x/v22/visitors/AsyncApi22DiffVisitor.java io/apitomy/datamodels/models/asyncapi/v2x/v22/visitors/AsyncApi22Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v22/visitors/AsyncApi22Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v22/visitors/AsyncApi22VisitorAdapter.java @@ -402,6 +405,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v23/io/AsyncApi23ModelReaderDispatcher io/apitomy/datamodels/models/asyncapi/v2x/v23/io/AsyncApi23ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v23/io/AsyncApi23ModelWriterDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v23/visitors/AsyncApi23DiffTraverser.java +io/apitomy/datamodels/models/asyncapi/v2x/v23/visitors/AsyncApi23DiffVisitor.java io/apitomy/datamodels/models/asyncapi/v2x/v23/visitors/AsyncApi23Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v23/visitors/AsyncApi23Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v23/visitors/AsyncApi23VisitorAdapter.java @@ -477,6 +481,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v24/io/AsyncApi24ModelReaderDispatcher io/apitomy/datamodels/models/asyncapi/v2x/v24/io/AsyncApi24ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v24/io/AsyncApi24ModelWriterDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v24/visitors/AsyncApi24DiffTraverser.java +io/apitomy/datamodels/models/asyncapi/v2x/v24/visitors/AsyncApi24DiffVisitor.java io/apitomy/datamodels/models/asyncapi/v2x/v24/visitors/AsyncApi24Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v24/visitors/AsyncApi24Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v24/visitors/AsyncApi24VisitorAdapter.java @@ -552,6 +557,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v25/io/AsyncApi25ModelReaderDispatcher io/apitomy/datamodels/models/asyncapi/v2x/v25/io/AsyncApi25ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v25/io/AsyncApi25ModelWriterDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v25/visitors/AsyncApi25DiffTraverser.java +io/apitomy/datamodels/models/asyncapi/v2x/v25/visitors/AsyncApi25DiffVisitor.java io/apitomy/datamodels/models/asyncapi/v2x/v25/visitors/AsyncApi25Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v25/visitors/AsyncApi25Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v25/visitors/AsyncApi25VisitorAdapter.java @@ -627,6 +633,7 @@ io/apitomy/datamodels/models/asyncapi/v2x/v26/io/AsyncApi26ModelReaderDispatcher io/apitomy/datamodels/models/asyncapi/v2x/v26/io/AsyncApi26ModelWriter.java io/apitomy/datamodels/models/asyncapi/v2x/v26/io/AsyncApi26ModelWriterDispatcher.java io/apitomy/datamodels/models/asyncapi/v2x/v26/visitors/AsyncApi26DiffTraverser.java +io/apitomy/datamodels/models/asyncapi/v2x/v26/visitors/AsyncApi26DiffVisitor.java io/apitomy/datamodels/models/asyncapi/v2x/v26/visitors/AsyncApi26Traverser.java io/apitomy/datamodels/models/asyncapi/v2x/v26/visitors/AsyncApi26Visitor.java io/apitomy/datamodels/models/asyncapi/v2x/v26/visitors/AsyncApi26VisitorAdapter.java @@ -744,6 +751,7 @@ io/apitomy/datamodels/models/asyncapi/v3x/v30/io/AsyncApi30ModelReaderDispatcher io/apitomy/datamodels/models/asyncapi/v3x/v30/io/AsyncApi30ModelWriter.java io/apitomy/datamodels/models/asyncapi/v3x/v30/io/AsyncApi30ModelWriterDispatcher.java io/apitomy/datamodels/models/asyncapi/v3x/v30/visitors/AsyncApi30DiffTraverser.java +io/apitomy/datamodels/models/asyncapi/v3x/v30/visitors/AsyncApi30DiffVisitor.java io/apitomy/datamodels/models/asyncapi/v3x/v30/visitors/AsyncApi30Traverser.java io/apitomy/datamodels/models/asyncapi/v3x/v30/visitors/AsyncApi30Visitor.java io/apitomy/datamodels/models/asyncapi/v3x/v30/visitors/AsyncApi30VisitorAdapter.java @@ -824,6 +832,7 @@ io/apitomy/datamodels/models/asyncapi/v3x/v31/io/AsyncApi31ModelReaderDispatcher io/apitomy/datamodels/models/asyncapi/v3x/v31/io/AsyncApi31ModelWriter.java io/apitomy/datamodels/models/asyncapi/v3x/v31/io/AsyncApi31ModelWriterDispatcher.java io/apitomy/datamodels/models/asyncapi/v3x/v31/visitors/AsyncApi31DiffTraverser.java +io/apitomy/datamodels/models/asyncapi/v3x/v31/visitors/AsyncApi31DiffVisitor.java io/apitomy/datamodels/models/asyncapi/v3x/v31/visitors/AsyncApi31Traverser.java io/apitomy/datamodels/models/asyncapi/v3x/v31/visitors/AsyncApi31Visitor.java io/apitomy/datamodels/models/asyncapi/v3x/v31/visitors/AsyncApi31VisitorAdapter.java @@ -856,6 +865,7 @@ io/apitomy/datamodels/models/jsonschema/draft/draft4/io/JD4ModelReaderDispatcher io/apitomy/datamodels/models/jsonschema/draft/draft4/io/JD4ModelWriter.java io/apitomy/datamodels/models/jsonschema/draft/draft4/io/JD4ModelWriterDispatcher.java io/apitomy/datamodels/models/jsonschema/draft/draft4/visitors/JD4DiffTraverser.java +io/apitomy/datamodels/models/jsonschema/draft/draft4/visitors/JD4DiffVisitor.java io/apitomy/datamodels/models/jsonschema/draft/draft4/visitors/JD4Traverser.java io/apitomy/datamodels/models/jsonschema/draft/draft4/visitors/JD4Visitor.java io/apitomy/datamodels/models/jsonschema/draft/draft4/visitors/JD4VisitorAdapter.java @@ -869,6 +879,7 @@ io/apitomy/datamodels/models/jsonschema/draft/draft6/io/JD6ModelReaderDispatcher io/apitomy/datamodels/models/jsonschema/draft/draft6/io/JD6ModelWriter.java io/apitomy/datamodels/models/jsonschema/draft/draft6/io/JD6ModelWriterDispatcher.java io/apitomy/datamodels/models/jsonschema/draft/draft6/visitors/JD6DiffTraverser.java +io/apitomy/datamodels/models/jsonschema/draft/draft6/visitors/JD6DiffVisitor.java io/apitomy/datamodels/models/jsonschema/draft/draft6/visitors/JD6Traverser.java io/apitomy/datamodels/models/jsonschema/draft/draft6/visitors/JD6Visitor.java io/apitomy/datamodels/models/jsonschema/draft/draft6/visitors/JD6VisitorAdapter.java @@ -882,6 +893,7 @@ io/apitomy/datamodels/models/jsonschema/draft/draft7/io/JD7ModelReaderDispatcher io/apitomy/datamodels/models/jsonschema/draft/draft7/io/JD7ModelWriter.java io/apitomy/datamodels/models/jsonschema/draft/draft7/io/JD7ModelWriterDispatcher.java io/apitomy/datamodels/models/jsonschema/draft/draft7/visitors/JD7DiffTraverser.java +io/apitomy/datamodels/models/jsonschema/draft/draft7/visitors/JD7DiffVisitor.java io/apitomy/datamodels/models/jsonschema/draft/draft7/visitors/JD7Traverser.java io/apitomy/datamodels/models/jsonschema/draft/draft7/visitors/JD7Visitor.java io/apitomy/datamodels/models/jsonschema/draft/draft7/visitors/JD7VisitorAdapter.java @@ -898,6 +910,7 @@ io/apitomy/datamodels/models/jsonschema/modern/v201909/io/JM201909ModelReaderDis io/apitomy/datamodels/models/jsonschema/modern/v201909/io/JM201909ModelWriter.java io/apitomy/datamodels/models/jsonschema/modern/v201909/io/JM201909ModelWriterDispatcher.java io/apitomy/datamodels/models/jsonschema/modern/v201909/visitors/JM201909DiffTraverser.java +io/apitomy/datamodels/models/jsonschema/modern/v201909/visitors/JM201909DiffVisitor.java io/apitomy/datamodels/models/jsonschema/modern/v201909/visitors/JM201909Traverser.java io/apitomy/datamodels/models/jsonschema/modern/v201909/visitors/JM201909Visitor.java io/apitomy/datamodels/models/jsonschema/modern/v201909/visitors/JM201909VisitorAdapter.java @@ -911,6 +924,7 @@ io/apitomy/datamodels/models/jsonschema/modern/v202012/io/JM202012ModelReaderDis io/apitomy/datamodels/models/jsonschema/modern/v202012/io/JM202012ModelWriter.java io/apitomy/datamodels/models/jsonschema/modern/v202012/io/JM202012ModelWriterDispatcher.java io/apitomy/datamodels/models/jsonschema/modern/v202012/visitors/JM202012DiffTraverser.java +io/apitomy/datamodels/models/jsonschema/modern/v202012/visitors/JM202012DiffVisitor.java io/apitomy/datamodels/models/jsonschema/modern/v202012/visitors/JM202012Traverser.java io/apitomy/datamodels/models/jsonschema/modern/v202012/visitors/JM202012Visitor.java io/apitomy/datamodels/models/jsonschema/modern/v202012/visitors/JM202012VisitorAdapter.java @@ -1049,6 +1063,7 @@ io/apitomy/datamodels/models/openapi/v2x/v20/io/OpenApi20ModelReaderDispatcher.j io/apitomy/datamodels/models/openapi/v2x/v20/io/OpenApi20ModelWriter.java io/apitomy/datamodels/models/openapi/v2x/v20/io/OpenApi20ModelWriterDispatcher.java io/apitomy/datamodels/models/openapi/v2x/v20/visitors/OpenApi20DiffTraverser.java +io/apitomy/datamodels/models/openapi/v2x/v20/visitors/OpenApi20DiffVisitor.java io/apitomy/datamodels/models/openapi/v2x/v20/visitors/OpenApi20Traverser.java io/apitomy/datamodels/models/openapi/v2x/v20/visitors/OpenApi20Visitor.java io/apitomy/datamodels/models/openapi/v2x/v20/visitors/OpenApi20VisitorAdapter.java @@ -1160,6 +1175,7 @@ io/apitomy/datamodels/models/openapi/v3x/v30/io/OpenApi30ModelReaderDispatcher.j io/apitomy/datamodels/models/openapi/v3x/v30/io/OpenApi30ModelWriter.java io/apitomy/datamodels/models/openapi/v3x/v30/io/OpenApi30ModelWriterDispatcher.java io/apitomy/datamodels/models/openapi/v3x/v30/visitors/OpenApi30DiffTraverser.java +io/apitomy/datamodels/models/openapi/v3x/v30/visitors/OpenApi30DiffVisitor.java io/apitomy/datamodels/models/openapi/v3x/v30/visitors/OpenApi30Traverser.java io/apitomy/datamodels/models/openapi/v3x/v30/visitors/OpenApi30Visitor.java io/apitomy/datamodels/models/openapi/v3x/v30/visitors/OpenApi30VisitorAdapter.java @@ -1235,6 +1251,7 @@ io/apitomy/datamodels/models/openapi/v3x/v31/io/OpenApi31ModelReaderDispatcher.j io/apitomy/datamodels/models/openapi/v3x/v31/io/OpenApi31ModelWriter.java io/apitomy/datamodels/models/openapi/v3x/v31/io/OpenApi31ModelWriterDispatcher.java io/apitomy/datamodels/models/openapi/v3x/v31/visitors/OpenApi31DiffTraverser.java +io/apitomy/datamodels/models/openapi/v3x/v31/visitors/OpenApi31DiffVisitor.java io/apitomy/datamodels/models/openapi/v3x/v31/visitors/OpenApi31Traverser.java io/apitomy/datamodels/models/openapi/v3x/v31/visitors/OpenApi31Visitor.java io/apitomy/datamodels/models/openapi/v3x/v31/visitors/OpenApi31VisitorAdapter.java @@ -1322,6 +1339,7 @@ io/apitomy/datamodels/models/openrpc/v1x/v13/io/OpenRpc13ModelReaderDispatcher.j io/apitomy/datamodels/models/openrpc/v1x/v13/io/OpenRpc13ModelWriter.java io/apitomy/datamodels/models/openrpc/v1x/v13/io/OpenRpc13ModelWriterDispatcher.java io/apitomy/datamodels/models/openrpc/v1x/v13/visitors/OpenRpc13DiffTraverser.java +io/apitomy/datamodels/models/openrpc/v1x/v13/visitors/OpenRpc13DiffVisitor.java io/apitomy/datamodels/models/openrpc/v1x/v13/visitors/OpenRpc13Traverser.java io/apitomy/datamodels/models/openrpc/v1x/v13/visitors/OpenRpc13Visitor.java io/apitomy/datamodels/models/openrpc/v1x/v13/visitors/OpenRpc13VisitorAdapter.java @@ -1367,6 +1385,7 @@ io/apitomy/datamodels/models/openrpc/v1x/v14/io/OpenRpc14ModelReaderDispatcher.j io/apitomy/datamodels/models/openrpc/v1x/v14/io/OpenRpc14ModelWriter.java io/apitomy/datamodels/models/openrpc/v1x/v14/io/OpenRpc14ModelWriterDispatcher.java io/apitomy/datamodels/models/openrpc/v1x/v14/visitors/OpenRpc14DiffTraverser.java +io/apitomy/datamodels/models/openrpc/v1x/v14/visitors/OpenRpc14DiffVisitor.java io/apitomy/datamodels/models/openrpc/v1x/v14/visitors/OpenRpc14Traverser.java io/apitomy/datamodels/models/openrpc/v1x/v14/visitors/OpenRpc14Visitor.java io/apitomy/datamodels/models/openrpc/v1x/v14/visitors/OpenRpc14VisitorAdapter.java diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index b727f16a1..a125a23fa 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -1,6 +1,16 @@ package io.test.synthetic.v1.visitors; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.test.synthetic.BooleanSchemaSchemaListUnion; +import io.test.synthetic.BooleanSchemaUnion; import io.test.synthetic.Node; +import io.test.synthetic.SchemaOrBoolean; +import io.test.synthetic.SynContact; +import io.test.synthetic.SynInfo; +import io.test.synthetic.SynItem; +import io.test.synthetic.SynOperation; +import io.test.synthetic.SynSchema; import io.test.synthetic.v1.Syn1Contact; import io.test.synthetic.v1.Syn1Document; import io.test.synthetic.v1.Syn1Info; @@ -10,11 +20,13 @@ import io.test.synthetic.v1.Syn1Paths; import io.test.synthetic.v1.Syn1Schema; import io.test.synthetic.visitors.diff.AbstractDiffTraverser; -import io.test.synthetic.visitors.diff.DiffVisitor; +import io.test.synthetic.visitors.diff.CollectionDiff; +import java.util.List; +import java.util.Map; -public class Syn1DiffTraverser extends AbstractDiffTraverser { +public class Syn1DiffTraverser extends AbstractDiffTraverser { - public Syn1DiffTraverser(DiffVisitor visitor) { + public Syn1DiffTraverser(Syn1DiffVisitor visitor) { super(visitor); } @@ -43,86 +55,139 @@ protected void traverseNode(Node original, Node updated) { public void traverseDocument(Syn1Document original, Syn1Document updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitDocument(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("version", original.getVersion(), updated.getVersion()); - this.diffEntityField("info", original.getInfo(), updated.getInfo()); - this.diffList("items", original.getItems(), updated.getItems()); - visitor.diffPrimitive("tags", original.getTags(), updated.getTags()); - visitor.diffPrimitive("metadata", original.getMetadata(), updated.getMetadata()); - this.diffUnionField("additionalSchema", original.getAdditionalSchema(), updated.getAdditionalSchema()); + visitor.diffDocumentVersion(original.getVersion(), updated.getVersion()); + visitor.diffDocumentInfo(original.getInfo(), updated.getInfo()); + if (original.getInfo() != null && updated.getInfo() != null) { + traverseNode(original.getInfo(), updated.getInfo()); + } + { + CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); + visitor.diffDocumentItems(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitDocumentItemsItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + if (pair.getOriginal() != null && pair.getUpdated() != null) { + traverseNode(pair.getOriginal(), pair.getUpdated()); + } + } + } + visitor.diffDocumentTags(original.getTags(), updated.getTags()); + visitor.diffDocumentMetadata(original.getMetadata(), updated.getMetadata()); + visitor.diffDocumentAdditionalSchema(original.getAdditionalSchema(), updated.getAdditionalSchema()); } public void traverseInfo(Syn1Info original, Syn1Info updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitInfo(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("name", original.getName(), updated.getName()); - this.diffEntityField("contact", original.getContact(), updated.getContact()); - visitor.diffPrimitive("version", original.getVersion(), updated.getVersion()); + visitor.diffInfoName(original.getName(), updated.getName()); + visitor.diffInfoContact(original.getContact(), updated.getContact()); + if (original.getContact() != null && updated.getContact() != null) { + traverseNode(original.getContact(), updated.getContact()); + } + visitor.diffInfoVersion(original.getVersion(), updated.getVersion()); } public void traverseContact(Syn1Contact original, Syn1Contact updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitContact(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("name", original.getName(), updated.getName()); - visitor.diffPrimitive("email", original.getEmail(), updated.getEmail()); - visitor.diffPrimitive("url", original.getUrl(), updated.getUrl()); + visitor.diffContactName(original.getName(), updated.getName()); + visitor.diffContactEmail(original.getEmail(), updated.getEmail()); + visitor.diffContactUrl(original.getUrl(), updated.getUrl()); } public void traverseItem(Syn1Item original, Syn1Item updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitItem(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); - visitor.diffPrimitive("description", original.getDescription(), updated.getDescription()); - visitor.diffPrimitive("required", original.isRequired(), updated.isRequired()); - visitor.diffPrimitive("order", original.getOrder(), updated.getOrder()); - visitor.diffPrimitive("weight", original.getWeight(), updated.getWeight()); - visitor.diffPrimitive("extra", original.getExtra(), updated.getExtra()); - visitor.diffPrimitive("raw", original.getRaw(), updated.getRaw()); - this.diffEntityField("schema", original.getSchema(), updated.getSchema()); - visitor.diffPrimitive("examples", original.getExamples(), updated.getExamples()); - this.diffUnionField("defaultValue", original.getDefaultValue(), updated.getDefaultValue()); - visitor.diffPrimitive("title", original.getTitle(), updated.getTitle()); + visitor.diffItem$ref(original.get$ref(), updated.get$ref()); + visitor.diffItemDescription(original.getDescription(), updated.getDescription()); + visitor.diffItemRequired(original.isRequired(), updated.isRequired()); + visitor.diffItemOrder(original.getOrder(), updated.getOrder()); + visitor.diffItemWeight(original.getWeight(), updated.getWeight()); + visitor.diffItemExtra(original.getExtra(), updated.getExtra()); + visitor.diffItemRaw(original.getRaw(), updated.getRaw()); + visitor.diffItemSchema(original.getSchema(), updated.getSchema()); + if (original.getSchema() != null && updated.getSchema() != null) { + traverseNode(original.getSchema(), updated.getSchema()); + } + visitor.diffItemExamples(original.getExamples(), updated.getExamples()); + visitor.diffItemDefaultValue(original.getDefaultValue(), updated.getDefaultValue()); + visitor.diffItemTitle(original.getTitle(), updated.getTitle()); } public void traverseSchema(Syn1Schema original, Syn1Schema updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitSchema(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); - visitor.diffPrimitive("type", original.getType(), updated.getType()); - this.diffUnionField("items", original.getItems(), updated.getItems()); - this.diffMap("properties", original.getProperties(), updated.getProperties()); - this.diffList("allOf", original.getAllOf(), updated.getAllOf()); - this.diffMap("definitions", original.getDefinitions(), updated.getDefinitions()); - this.diffMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); - this.diffList("composedSchemas", original.getComposedSchemas(), updated.getComposedSchemas()); - visitor.diffPrimitive("minLength", original.getMinLength(), updated.getMinLength()); - visitor.diffPrimitive("maxLength", original.getMaxLength(), updated.getMaxLength()); - visitor.diffPrimitive("enum", original.getEnum(), updated.getEnum()); + visitor.diffSchema$ref(original.get$ref(), updated.get$ref()); + visitor.diffSchemaType(original.getType(), updated.getType()); + visitor.diffSchemaItems(original.getItems(), updated.getItems()); + { + CollectionDiff diff = this.pairMap("properties", original.getProperties(), + updated.getProperties()); + visitor.diffSchemaProperties(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaProperties(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + { + CollectionDiff diff = this.pairList("allOf", original.getAllOf(), + updated.getAllOf()); + visitor.diffSchemaAllOf(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaAllOfItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + { + CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), + updated.getDefinitions()); + visitor.diffSchemaDefinitions(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaDefinitions(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + { + CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), + updated.getNestedSchemas()); + visitor.diffSchemaNestedSchemas(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaNestedSchemas(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + { + CollectionDiff diff = this.pairList("composedSchemas", + original.getComposedSchemas(), updated.getComposedSchemas()); + visitor.diffSchemaComposedSchemas(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaComposedSchemasItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + visitor.diffSchemaMinLength(original.getMinLength(), updated.getMinLength()); + visitor.diffSchemaMaxLength(original.getMaxLength(), updated.getMaxLength()); + visitor.diffSchemaEnum(original.getEnum(), updated.getEnum()); } public void traversePaths(Syn1Paths original, Syn1Paths updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitPaths(original, updated)) return; if (original == null || updated == null) return; @@ -131,27 +196,46 @@ public void traversePaths(Syn1Paths original, Syn1Paths updated) { public void traversePathItem(Syn1PathItem original, Syn1PathItem updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitPathItem(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); - visitor.diffPrimitive("summary", original.getSummary(), updated.getSummary()); - this.diffEntityField("get", original.getGet(), updated.getGet()); - this.diffEntityField("put", original.getPut(), updated.getPut()); - this.diffEntityField("post", original.getPost(), updated.getPost()); + visitor.diffPathItem$ref(original.get$ref(), updated.get$ref()); + visitor.diffPathItemSummary(original.getSummary(), updated.getSummary()); + visitor.diffPathItemGet(original.getGet(), updated.getGet()); + if (original.getGet() != null && updated.getGet() != null) { + traverseNode(original.getGet(), updated.getGet()); + } + visitor.diffPathItemPut(original.getPut(), updated.getPut()); + if (original.getPut() != null && updated.getPut() != null) { + traverseNode(original.getPut(), updated.getPut()); + } + visitor.diffPathItemPost(original.getPost(), updated.getPost()); + if (original.getPost() != null && updated.getPost() != null) { + traverseNode(original.getPost(), updated.getPost()); + } } public void traverseOperation(Syn1Operation original, Syn1Operation updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitOperation(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("operationId", original.getOperationId(), updated.getOperationId()); - visitor.diffPrimitive("summary", original.getSummary(), updated.getSummary()); - visitor.diffPrimitive("tags", original.getTags(), updated.getTags()); - this.diffList("parameters", original.getParameters(), updated.getParameters()); + visitor.diffOperationOperationId(original.getOperationId(), updated.getOperationId()); + visitor.diffOperationSummary(original.getSummary(), updated.getSummary()); + visitor.diffOperationTags(original.getTags(), updated.getTags()); + { + CollectionDiff diff = this.pairList("parameters", original.getParameters(), + updated.getParameters()); + visitor.diffOperationParameters(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitOperationParametersItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + if (pair.getOriginal() != null && pair.getUpdated() != null) { + traverseNode(pair.getOriginal(), pair.getUpdated()); + } + } + } } } \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java new file mode 100644 index 000000000..e13caca84 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java @@ -0,0 +1,209 @@ +package io.test.synthetic.v1.visitors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.test.synthetic.BooleanSchemaSchemaListUnion; +import io.test.synthetic.BooleanSchemaUnion; +import io.test.synthetic.SchemaOrBoolean; +import io.test.synthetic.SynContact; +import io.test.synthetic.SynInfo; +import io.test.synthetic.SynItem; +import io.test.synthetic.SynOperation; +import io.test.synthetic.SynSchema; +import io.test.synthetic.v1.Syn1Contact; +import io.test.synthetic.v1.Syn1Document; +import io.test.synthetic.v1.Syn1Info; +import io.test.synthetic.v1.Syn1Item; +import io.test.synthetic.v1.Syn1Operation; +import io.test.synthetic.v1.Syn1PathItem; +import io.test.synthetic.v1.Syn1Paths; +import io.test.synthetic.v1.Syn1Schema; +import io.test.synthetic.visitors.diff.CollectionDiff; +import io.test.synthetic.visitors.diff.DiffVisitor; +import java.util.List; +import java.util.Map; + +public abstract class Syn1DiffVisitor extends DiffVisitor { + + public boolean visitDocument(Syn1Document original, Syn1Document updated) { + return true; + } + + public void diffDocumentVersion(String original, String updated) { + } + + public void diffDocumentInfo(SynInfo original, SynInfo updated) { + } + + public void diffDocumentItems(CollectionDiff diff) { + } + + public void visitDocumentItemsItem(Integer index, SynItem original, SynItem updated) { + } + + public void diffDocumentTags(List original, List updated) { + } + + public void diffDocumentMetadata(Map original, Map updated) { + } + + public void diffDocumentAdditionalSchema(SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public boolean visitInfo(Syn1Info original, Syn1Info updated) { + return true; + } + + public void diffInfoName(String original, String updated) { + } + + public void diffInfoContact(SynContact original, SynContact updated) { + } + + public void diffInfoVersion(String original, String updated) { + } + + public boolean visitContact(Syn1Contact original, Syn1Contact updated) { + return true; + } + + public void diffContactName(String original, String updated) { + } + + public void diffContactEmail(String original, String updated) { + } + + public void diffContactUrl(String original, String updated) { + } + + public boolean visitItem(Syn1Item original, Syn1Item updated) { + return true; + } + + public void diffItem$ref(String original, String updated) { + } + + public void diffItemDescription(String original, String updated) { + } + + public void diffItemRequired(Boolean original, Boolean updated) { + } + + public void diffItemOrder(Integer original, Integer updated) { + } + + public void diffItemWeight(Number original, Number updated) { + } + + public void diffItemExtra(JsonNode original, JsonNode updated) { + } + + public void diffItemRaw(ObjectNode original, ObjectNode updated) { + } + + public void diffItemSchema(SynSchema original, SynSchema updated) { + } + + public void diffItemExamples(List original, List updated) { + } + + public void diffItemDefaultValue(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffItemTitle(String original, String updated) { + } + + public boolean visitSchema(Syn1Schema original, Syn1Schema updated) { + return true; + } + + public void diffSchema$ref(String original, String updated) { + } + + public void diffSchemaType(String original, String updated) { + } + + public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchemaSchemaListUnion updated) { + } + + public void diffSchemaProperties(CollectionDiff diff) { + } + + public void visitSchemaProperties(String key, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaAllOf(CollectionDiff diff) { + } + + public void visitSchemaAllOfItem(Integer index, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaDefinitions(CollectionDiff diff) { + } + + public void visitSchemaDefinitions(String key, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaNestedSchemas(CollectionDiff diff) { + } + + public void visitSchemaNestedSchemas(String key, SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public void diffSchemaComposedSchemas(CollectionDiff diff) { + } + + public void visitSchemaComposedSchemasItem(Integer index, SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public void diffSchemaMinLength(Integer original, Integer updated) { + } + + public void diffSchemaMaxLength(Integer original, Integer updated) { + } + + public void diffSchemaEnum(List original, List updated) { + } + + public boolean visitPaths(Syn1Paths original, Syn1Paths updated) { + return true; + } + + public boolean visitPathItem(Syn1PathItem original, Syn1PathItem updated) { + return true; + } + + public void diffPathItem$ref(String original, String updated) { + } + + public void diffPathItemSummary(String original, String updated) { + } + + public void diffPathItemGet(SynOperation original, SynOperation updated) { + } + + public void diffPathItemPut(SynOperation original, SynOperation updated) { + } + + public void diffPathItemPost(SynOperation original, SynOperation updated) { + } + + public boolean visitOperation(Syn1Operation original, Syn1Operation updated) { + return true; + } + + public void diffOperationOperationId(String original, String updated) { + } + + public void diffOperationSummary(String original, String updated) { + } + + public void diffOperationTags(List original, List updated) { + } + + public void diffOperationParameters(CollectionDiff diff) { + } + + public void visitOperationParametersItem(Integer index, SynItem original, SynItem updated) { + } +} \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index 76ab720b1..2772930f7 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -1,6 +1,16 @@ package io.test.synthetic.v2.visitors; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.test.synthetic.BooleanSchemaSchemaListUnion; +import io.test.synthetic.BooleanSchemaUnion; import io.test.synthetic.Node; +import io.test.synthetic.SchemaOrBoolean; +import io.test.synthetic.SynContact; +import io.test.synthetic.SynInfo; +import io.test.synthetic.SynItem; +import io.test.synthetic.SynOperation; +import io.test.synthetic.SynSchema; import io.test.synthetic.v2.Syn2Contact; import io.test.synthetic.v2.Syn2Document; import io.test.synthetic.v2.Syn2Info; @@ -10,11 +20,13 @@ import io.test.synthetic.v2.Syn2Paths; import io.test.synthetic.v2.Syn2Schema; import io.test.synthetic.visitors.diff.AbstractDiffTraverser; -import io.test.synthetic.visitors.diff.DiffVisitor; +import io.test.synthetic.visitors.diff.CollectionDiff; +import java.util.List; +import java.util.Map; -public class Syn2DiffTraverser extends AbstractDiffTraverser { +public class Syn2DiffTraverser extends AbstractDiffTraverser { - public Syn2DiffTraverser(DiffVisitor visitor) { + public Syn2DiffTraverser(Syn2DiffVisitor visitor) { super(visitor); } @@ -43,89 +55,152 @@ protected void traverseNode(Node original, Node updated) { public void traverseDocument(Syn2Document original, Syn2Document updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitDocument(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("version", original.getVersion(), updated.getVersion()); - this.diffEntityField("info", original.getInfo(), updated.getInfo()); - this.diffList("items", original.getItems(), updated.getItems()); - visitor.diffPrimitive("tags", original.getTags(), updated.getTags()); - visitor.diffPrimitive("metadata", original.getMetadata(), updated.getMetadata()); - this.diffMap("webhooks", original.getWebhooks(), updated.getWebhooks()); - this.diffUnionField("additionalSchema", original.getAdditionalSchema(), updated.getAdditionalSchema()); + visitor.diffDocumentVersion(original.getVersion(), updated.getVersion()); + visitor.diffDocumentInfo(original.getInfo(), updated.getInfo()); + if (original.getInfo() != null && updated.getInfo() != null) { + traverseNode(original.getInfo(), updated.getInfo()); + } + { + CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); + visitor.diffDocumentItems(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitDocumentItemsItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + if (pair.getOriginal() != null && pair.getUpdated() != null) { + traverseNode(pair.getOriginal(), pair.getUpdated()); + } + } + } + visitor.diffDocumentTags(original.getTags(), updated.getTags()); + visitor.diffDocumentMetadata(original.getMetadata(), updated.getMetadata()); + { + CollectionDiff diff = this.pairMap("webhooks", original.getWebhooks(), + updated.getWebhooks()); + visitor.diffDocumentWebhooks(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitDocumentWebhooks(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + if (pair.getOriginal() != null && pair.getUpdated() != null) { + traverseNode(pair.getOriginal(), pair.getUpdated()); + } + } + } + visitor.diffDocumentAdditionalSchema(original.getAdditionalSchema(), updated.getAdditionalSchema()); } public void traverseInfo(Syn2Info original, Syn2Info updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitInfo(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("name", original.getName(), updated.getName()); - this.diffEntityField("contact", original.getContact(), updated.getContact()); - visitor.diffPrimitive("version", original.getVersion(), updated.getVersion()); - visitor.diffPrimitive("license", original.getLicense(), updated.getLicense()); + visitor.diffInfoName(original.getName(), updated.getName()); + visitor.diffInfoContact(original.getContact(), updated.getContact()); + if (original.getContact() != null && updated.getContact() != null) { + traverseNode(original.getContact(), updated.getContact()); + } + visitor.diffInfoVersion(original.getVersion(), updated.getVersion()); + visitor.diffInfoLicense(original.getLicense(), updated.getLicense()); } public void traverseContact(Syn2Contact original, Syn2Contact updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitContact(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("name", original.getName(), updated.getName()); - visitor.diffPrimitive("email", original.getEmail(), updated.getEmail()); - visitor.diffPrimitive("url", original.getUrl(), updated.getUrl()); + visitor.diffContactName(original.getName(), updated.getName()); + visitor.diffContactEmail(original.getEmail(), updated.getEmail()); + visitor.diffContactUrl(original.getUrl(), updated.getUrl()); } public void traverseItem(Syn2Item original, Syn2Item updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitItem(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); - visitor.diffPrimitive("description", original.getDescription(), updated.getDescription()); - visitor.diffPrimitive("required", original.isRequired(), updated.isRequired()); - visitor.diffPrimitive("order", original.getOrder(), updated.getOrder()); - visitor.diffPrimitive("weight", original.getWeight(), updated.getWeight()); - visitor.diffPrimitive("extra", original.getExtra(), updated.getExtra()); - visitor.diffPrimitive("raw", original.getRaw(), updated.getRaw()); - this.diffEntityField("schema", original.getSchema(), updated.getSchema()); - visitor.diffPrimitive("examples", original.getExamples(), updated.getExamples()); - this.diffUnionField("defaultValue", original.getDefaultValue(), updated.getDefaultValue()); - visitor.diffPrimitive("title", original.getTitle(), updated.getTitle()); - visitor.diffPrimitive("deprecated", original.isDeprecated(), updated.isDeprecated()); + visitor.diffItem$ref(original.get$ref(), updated.get$ref()); + visitor.diffItemDescription(original.getDescription(), updated.getDescription()); + visitor.diffItemRequired(original.isRequired(), updated.isRequired()); + visitor.diffItemOrder(original.getOrder(), updated.getOrder()); + visitor.diffItemWeight(original.getWeight(), updated.getWeight()); + visitor.diffItemExtra(original.getExtra(), updated.getExtra()); + visitor.diffItemRaw(original.getRaw(), updated.getRaw()); + visitor.diffItemSchema(original.getSchema(), updated.getSchema()); + if (original.getSchema() != null && updated.getSchema() != null) { + traverseNode(original.getSchema(), updated.getSchema()); + } + visitor.diffItemExamples(original.getExamples(), updated.getExamples()); + visitor.diffItemDefaultValue(original.getDefaultValue(), updated.getDefaultValue()); + visitor.diffItemTitle(original.getTitle(), updated.getTitle()); + visitor.diffItemDeprecated(original.isDeprecated(), updated.isDeprecated()); } public void traverseSchema(Syn2Schema original, Syn2Schema updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitSchema(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); - visitor.diffPrimitive("type", original.getType(), updated.getType()); - this.diffUnionField("items", original.getItems(), updated.getItems()); - this.diffMap("properties", original.getProperties(), updated.getProperties()); - this.diffList("allOf", original.getAllOf(), updated.getAllOf()); - this.diffMap("definitions", original.getDefinitions(), updated.getDefinitions()); - this.diffMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); - this.diffList("composedSchemas", original.getComposedSchemas(), updated.getComposedSchemas()); - visitor.diffPrimitive("minLength", original.getMinLength(), updated.getMinLength()); - visitor.diffPrimitive("maxLength", original.getMaxLength(), updated.getMaxLength()); - visitor.diffPrimitive("enum", original.getEnum(), updated.getEnum()); + visitor.diffSchema$ref(original.get$ref(), updated.get$ref()); + visitor.diffSchemaType(original.getType(), updated.getType()); + visitor.diffSchemaItems(original.getItems(), updated.getItems()); + { + CollectionDiff diff = this.pairMap("properties", original.getProperties(), + updated.getProperties()); + visitor.diffSchemaProperties(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaProperties(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + { + CollectionDiff diff = this.pairList("allOf", original.getAllOf(), + updated.getAllOf()); + visitor.diffSchemaAllOf(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaAllOfItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + { + CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), + updated.getDefinitions()); + visitor.diffSchemaDefinitions(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaDefinitions(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + { + CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), + updated.getNestedSchemas()); + visitor.diffSchemaNestedSchemas(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaNestedSchemas(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + { + CollectionDiff diff = this.pairList("composedSchemas", + original.getComposedSchemas(), updated.getComposedSchemas()); + visitor.diffSchemaComposedSchemas(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitSchemaComposedSchemasItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + } + } + visitor.diffSchemaMinLength(original.getMinLength(), updated.getMinLength()); + visitor.diffSchemaMaxLength(original.getMaxLength(), updated.getMaxLength()); + visitor.diffSchemaEnum(original.getEnum(), updated.getEnum()); } public void traversePaths(Syn2Paths original, Syn2Paths updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitPaths(original, updated)) return; if (original == null || updated == null) return; @@ -134,28 +209,50 @@ public void traversePaths(Syn2Paths original, Syn2Paths updated) { public void traversePathItem(Syn2PathItem original, Syn2PathItem updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitPathItem(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("$ref", original.get$ref(), updated.get$ref()); - visitor.diffPrimitive("summary", original.getSummary(), updated.getSummary()); - this.diffEntityField("get", original.getGet(), updated.getGet()); - this.diffEntityField("put", original.getPut(), updated.getPut()); - this.diffEntityField("post", original.getPost(), updated.getPost()); - this.diffEntityField("delete", original.getDelete(), updated.getDelete()); + visitor.diffPathItem$ref(original.get$ref(), updated.get$ref()); + visitor.diffPathItemSummary(original.getSummary(), updated.getSummary()); + visitor.diffPathItemGet(original.getGet(), updated.getGet()); + if (original.getGet() != null && updated.getGet() != null) { + traverseNode(original.getGet(), updated.getGet()); + } + visitor.diffPathItemPut(original.getPut(), updated.getPut()); + if (original.getPut() != null && updated.getPut() != null) { + traverseNode(original.getPut(), updated.getPut()); + } + visitor.diffPathItemPost(original.getPost(), updated.getPost()); + if (original.getPost() != null && updated.getPost() != null) { + traverseNode(original.getPost(), updated.getPost()); + } + visitor.diffPathItemDelete(original.getDelete(), updated.getDelete()); + if (original.getDelete() != null && updated.getDelete() != null) { + traverseNode(original.getDelete(), updated.getDelete()); + } } public void traverseOperation(Syn2Operation original, Syn2Operation updated) { if (original == null && updated == null) return; - if (!visitor.visitEntityPair(original, updated)) + if (!visitor.visitOperation(original, updated)) return; if (original == null || updated == null) return; - visitor.diffPrimitive("operationId", original.getOperationId(), updated.getOperationId()); - visitor.diffPrimitive("summary", original.getSummary(), updated.getSummary()); - visitor.diffPrimitive("tags", original.getTags(), updated.getTags()); - this.diffList("parameters", original.getParameters(), updated.getParameters()); + visitor.diffOperationOperationId(original.getOperationId(), updated.getOperationId()); + visitor.diffOperationSummary(original.getSummary(), updated.getSummary()); + visitor.diffOperationTags(original.getTags(), updated.getTags()); + { + CollectionDiff diff = this.pairList("parameters", original.getParameters(), + updated.getParameters()); + visitor.diffOperationParameters(diff); + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + visitor.visitOperationParametersItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + if (pair.getOriginal() != null && pair.getUpdated() != null) { + traverseNode(pair.getOriginal(), pair.getUpdated()); + } + } + } } } \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java new file mode 100644 index 000000000..8809bdd2a --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java @@ -0,0 +1,224 @@ +package io.test.synthetic.v2.visitors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.test.synthetic.BooleanSchemaSchemaListUnion; +import io.test.synthetic.BooleanSchemaUnion; +import io.test.synthetic.SchemaOrBoolean; +import io.test.synthetic.SynContact; +import io.test.synthetic.SynInfo; +import io.test.synthetic.SynItem; +import io.test.synthetic.SynOperation; +import io.test.synthetic.SynSchema; +import io.test.synthetic.v2.Syn2Contact; +import io.test.synthetic.v2.Syn2Document; +import io.test.synthetic.v2.Syn2Info; +import io.test.synthetic.v2.Syn2Item; +import io.test.synthetic.v2.Syn2Operation; +import io.test.synthetic.v2.Syn2PathItem; +import io.test.synthetic.v2.Syn2Paths; +import io.test.synthetic.v2.Syn2Schema; +import io.test.synthetic.visitors.diff.CollectionDiff; +import io.test.synthetic.visitors.diff.DiffVisitor; +import java.util.List; +import java.util.Map; + +public abstract class Syn2DiffVisitor extends DiffVisitor { + + public boolean visitDocument(Syn2Document original, Syn2Document updated) { + return true; + } + + public void diffDocumentVersion(String original, String updated) { + } + + public void diffDocumentInfo(SynInfo original, SynInfo updated) { + } + + public void diffDocumentItems(CollectionDiff diff) { + } + + public void visitDocumentItemsItem(Integer index, SynItem original, SynItem updated) { + } + + public void diffDocumentTags(List original, List updated) { + } + + public void diffDocumentMetadata(Map original, Map updated) { + } + + public void diffDocumentWebhooks(CollectionDiff diff) { + } + + public void visitDocumentWebhooks(String key, Syn2PathItem original, Syn2PathItem updated) { + } + + public void diffDocumentAdditionalSchema(SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public boolean visitInfo(Syn2Info original, Syn2Info updated) { + return true; + } + + public void diffInfoName(String original, String updated) { + } + + public void diffInfoContact(SynContact original, SynContact updated) { + } + + public void diffInfoVersion(String original, String updated) { + } + + public void diffInfoLicense(String original, String updated) { + } + + public boolean visitContact(Syn2Contact original, Syn2Contact updated) { + return true; + } + + public void diffContactName(String original, String updated) { + } + + public void diffContactEmail(String original, String updated) { + } + + public void diffContactUrl(String original, String updated) { + } + + public boolean visitItem(Syn2Item original, Syn2Item updated) { + return true; + } + + public void diffItem$ref(String original, String updated) { + } + + public void diffItemDescription(String original, String updated) { + } + + public void diffItemRequired(Boolean original, Boolean updated) { + } + + public void diffItemOrder(Integer original, Integer updated) { + } + + public void diffItemWeight(Number original, Number updated) { + } + + public void diffItemExtra(JsonNode original, JsonNode updated) { + } + + public void diffItemRaw(ObjectNode original, ObjectNode updated) { + } + + public void diffItemSchema(SynSchema original, SynSchema updated) { + } + + public void diffItemExamples(List original, List updated) { + } + + public void diffItemDefaultValue(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffItemTitle(String original, String updated) { + } + + public void diffItemDeprecated(Boolean original, Boolean updated) { + } + + public boolean visitSchema(Syn2Schema original, Syn2Schema updated) { + return true; + } + + public void diffSchema$ref(String original, String updated) { + } + + public void diffSchemaType(String original, String updated) { + } + + public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchemaSchemaListUnion updated) { + } + + public void diffSchemaProperties(CollectionDiff diff) { + } + + public void visitSchemaProperties(String key, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaAllOf(CollectionDiff diff) { + } + + public void visitSchemaAllOfItem(Integer index, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaDefinitions(CollectionDiff diff) { + } + + public void visitSchemaDefinitions(String key, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaNestedSchemas(CollectionDiff diff) { + } + + public void visitSchemaNestedSchemas(String key, SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public void diffSchemaComposedSchemas(CollectionDiff diff) { + } + + public void visitSchemaComposedSchemasItem(Integer index, SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public void diffSchemaMinLength(Integer original, Integer updated) { + } + + public void diffSchemaMaxLength(Integer original, Integer updated) { + } + + public void diffSchemaEnum(List original, List updated) { + } + + public boolean visitPaths(Syn2Paths original, Syn2Paths updated) { + return true; + } + + public boolean visitPathItem(Syn2PathItem original, Syn2PathItem updated) { + return true; + } + + public void diffPathItem$ref(String original, String updated) { + } + + public void diffPathItemSummary(String original, String updated) { + } + + public void diffPathItemGet(SynOperation original, SynOperation updated) { + } + + public void diffPathItemPut(SynOperation original, SynOperation updated) { + } + + public void diffPathItemPost(SynOperation original, SynOperation updated) { + } + + public void diffPathItemDelete(Syn2Operation original, Syn2Operation updated) { + } + + public boolean visitOperation(Syn2Operation original, Syn2Operation updated) { + return true; + } + + public void diffOperationOperationId(String original, String updated) { + } + + public void diffOperationSummary(String original, String updated) { + } + + public void diffOperationTags(List original, List updated) { + } + + public void diffOperationParameters(CollectionDiff diff) { + } + + public void visitOperationParametersItem(Integer index, SynItem original, SynItem updated) { + } +} \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java index 7ef8fb998..572f9d8f2 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java @@ -1,74 +1,42 @@ package io.test.synthetic.visitors.diff; -import io.test.synthetic.Any; import io.test.synthetic.Node; -import io.test.synthetic.union.Union; import java.util.List; import java.util.Map; /** * Base class for generated diff traversers. Provides shared collection diffing * logic. Subclasses (generated per spec version) provide entity-specific field - * iteration. + * iteration and call typed visitor methods. + * + * @param + * the spec-version-specific DiffVisitor subclass */ -public class AbstractDiffTraverser { +public class AbstractDiffTraverser { - protected final DiffVisitor visitor; + protected final V visitor; - protected AbstractDiffTraverser(DiffVisitor visitor) { + protected AbstractDiffTraverser(V visitor) { this.visitor = visitor; } @SuppressWarnings("unchecked") - protected void diffMap(String propertyName, Map original, Map updated) { - PairingStrategy strategy = (PairingStrategy) visitor.getPairingStrategy(propertyName); + protected CollectionDiff pairMap(String propertyName, Map original, + Map updated) { + PairingStrategy strategy = (PairingStrategy) visitor.getPairingStrategy(propertyName); if (strategy == null) { strategy = new KeyPairingStrategy<>(); } - - CollectionDiff diff = strategy.pair(original != null ? original : Map.of(), - updated != null ? updated : Map.of()); - visitor.diffMap(propertyName, diff); - - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - recurseIntoPair(pair.getOriginal(), pair.getUpdated()); - } + return strategy.pair(original != null ? original : Map.of(), updated != null ? updated : Map.of()); } @SuppressWarnings("unchecked") - protected void diffList(String propertyName, List original, List updated) { - PairingStrategy strategy = (PairingStrategy) visitor.getPairingStrategy(propertyName); + protected CollectionDiff pairList(String propertyName, List original, List updated) { + PairingStrategy strategy = (PairingStrategy) visitor.getPairingStrategy(propertyName); if (strategy == null) { strategy = new IndexPairingStrategy<>(); } - - CollectionDiff diff = strategy.pair(IndexPairingStrategy.toIndexMap(original), - IndexPairingStrategy.toIndexMap(updated)); - visitor.diffList(propertyName, diff); - - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - recurseIntoPair(pair.getOriginal(), pair.getUpdated()); - } - } - - protected void diffEntityField(String propertyName, Node original, Node updated) { - visitor.diffEntity(propertyName, original, updated); - if (original != null && updated != null) { - traverseNode(original, updated); - } - } - - protected void diffUnionField(String propertyName, Union original, Union updated) { - visitor.diffUnion(propertyName, original, updated); - } - - private void recurseIntoPair(Object original, Object updated) { - if (original instanceof Node && updated instanceof Node) { - traverseNode((Node) original, (Node) updated); - } else if (original instanceof Union && updated instanceof Union) { - // Union pairs in collections — visitor handles dispatch - visitor.diffUnion(null, (Union) original, (Union) updated); - } + return strategy.pair(IndexPairingStrategy.toIndexMap(original), IndexPairingStrategy.toIndexMap(updated)); } /** diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java index 325a4661f..0332166a8 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java @@ -1,60 +1,14 @@ package io.test.synthetic.visitors.diff; -import io.test.synthetic.Node; -import io.test.synthetic.union.Union; - /** - * Visitor for paired tree traversal. The generated DiffTraverser walks two - * trees in parallel, calling these methods for each field. Subclasses override - * the methods they care about. + * Base class for generated per-spec-version DiffVisitors. Generated subclasses + * add typed, field-specific methods for each entity and property. * *

* The traverser auto-recurses into entity fields and matched collection pairs. - * Override {@link #visitEntityPair} to skip recursion for specific entities. */ public abstract class DiffVisitor { - /** - * Called before traversing fields of an entity pair. Return false to skip - * field-level traversal of this pair. - */ - public boolean visitEntityPair(Node original, Node updated) { - return true; - } - - /** - * Called for each primitive-typed field (String, Boolean, Number, etc.). - */ - public void diffPrimitive(String propertyName, Object original, Object updated) { - } - - /** - * Called for each entity-typed field. The traverser auto-recurses into both - * entities after this call (if both are non-null). - */ - public void diffEntity(String propertyName, Node original, Node updated) { - } - - /** - * Called for each union-typed field. - */ - public void diffUnion(String propertyName, Union original, Union updated) { - } - - /** - * Called for each list-typed field with the pairing result. The traverser - * auto-recurses into matched entity/union pairs after this call. - */ - public void diffList(String propertyName, CollectionDiff diff) { - } - - /** - * Called for each map-typed field with the pairing result. The traverser - * auto-recurses into matched entity/union pairs after this call. - */ - public void diffMap(String propertyName, CollectionDiff diff) { - } - /** * Returns the pairing strategy for a collection field, or null to use the * default (key-based for maps, index-based for lists). diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java index f6dd79945..66cba0d4b 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java @@ -1,40 +1,18 @@ package io.test.synthetic.visitors.diff; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** - * Pairs list entries by index. Items at the same index are paired; extra items - * in the longer list are added/removed. + * Pairs list entries by index. Converts lists to index-keyed maps, then + * delegates to the shared pairing logic. */ public class IndexPairingStrategy implements PairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { - Map orig = original != null ? original : new LinkedHashMap<>(); - Map upd = updated != null ? updated : new LinkedHashMap<>(); - - int maxIndex = Math.max(orig.size(), upd.size()); - - List> added = new ArrayList<>(); - List> removed = new ArrayList<>(); - List> matched = new ArrayList<>(); - - for (int i = 0; i < maxIndex; i++) { - boolean inOrig = orig.containsKey(i); - boolean inUpd = upd.containsKey(i); - if (inOrig && inUpd) { - matched.add(new CollectionDiff.MatchedPair<>(i, orig.get(i), upd.get(i))); - } else if (inUpd) { - added.add(new CollectionDiff.Entry<>(i, upd.get(i))); - } else { - removed.add(new CollectionDiff.Entry<>(i, orig.get(i))); - } - } - - return new CollectionDiff<>(added, removed, matched); + return PairingStrategy.pairByKey(original, updated); } /** diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java index 43b04aaa9..9f4b5b122 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java @@ -1,43 +1,14 @@ package io.test.synthetic.visitors.diff; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; -import java.util.Set; /** - * Pairs map entries by key. Entries with matching keys are paired; keys present - * in only one map are added/removed. + * Pairs map entries by key. */ public class KeyPairingStrategy implements PairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { - Map orig = original != null ? original : new LinkedHashMap<>(); - Map upd = updated != null ? updated : new LinkedHashMap<>(); - - Set allKeys = new LinkedHashSet<>(); - allKeys.addAll(orig.keySet()); - allKeys.addAll(upd.keySet()); - - List> added = new ArrayList<>(); - List> removed = new ArrayList<>(); - List> matched = new ArrayList<>(); - - for (String key : allKeys) { - boolean inOrig = orig.containsKey(key); - boolean inUpd = upd.containsKey(key); - if (inOrig && inUpd) { - matched.add(new CollectionDiff.MatchedPair<>(key, orig.get(key), upd.get(key))); - } else if (inUpd) { - added.add(new CollectionDiff.Entry<>(key, upd.get(key))); - } else { - removed.add(new CollectionDiff.Entry<>(key, orig.get(key))); - } - } - - return new CollectionDiff<>(added, removed, matched); + return PairingStrategy.pairByKey(original, updated); } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java index 8929be83c..87082d2d1 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java @@ -1,5 +1,12 @@ package io.test.synthetic.visitors.diff; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Strategy for pairing elements from two collections to produce a * {@link CollectionDiff}. @@ -11,5 +18,32 @@ */ public interface PairingStrategy { - CollectionDiff pair(java.util.Map original, java.util.Map updated); + CollectionDiff pair(Map original, Map updated); + + static CollectionDiff pairByKey(Map original, Map updated) { + Map orig = original != null ? original : new LinkedHashMap<>(); + Map upd = updated != null ? updated : new LinkedHashMap<>(); + + Set allKeys = new LinkedHashSet<>(); + allKeys.addAll(orig.keySet()); + allKeys.addAll(upd.keySet()); + + List> added = new ArrayList<>(); + List> removed = new ArrayList<>(); + List> matched = new ArrayList<>(); + + for (K key : allKeys) { + boolean inOrig = orig.containsKey(key); + boolean inUpd = upd.containsKey(key); + if (inOrig && inUpd) { + matched.add(new CollectionDiff.MatchedPair<>(key, orig.get(key), upd.get(key))); + } else if (inUpd) { + added.add(new CollectionDiff.Entry<>(key, upd.get(key))); + } else { + removed.add(new CollectionDiff.Entry<>(key, orig.get(key))); + } + } + + return new CollectionDiff<>(added, removed, matched); + } } From 7c236df781092ef7c24894d862784ef27e3c3845 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 14:44:20 +0200 Subject: [PATCH 04/16] refactor: split PairingStrategy into MapPairingStrategy and ListPairingStrategy (C36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MapPairingStrategy.pair(Map, Map) — for map fields - ListPairingStrategy.pair(List, List) — for list fields, handles index conversion internally - CollectionDiff.pairByKey() — shared static factory for the common key-based pairing logic - DiffVisitor: getMapPairingStrategy/getListPairingStrategy replace single getPairingStrategy --- .../umg/pipe/java/LoadBaseClassesStage.java | 2 + .../visitors/diff/AbstractDiffTraverser.java | 18 +++---- .../base/visitors/diff/CollectionDiff.java | 35 +++++++++++++ .../umg/base/visitors/diff/DiffVisitor.java | 16 ++++-- .../visitors/diff/IndexPairingStrategy.java | 14 ++---- .../visitors/diff/KeyPairingStrategy.java | 6 +-- .../visitors/diff/ListPairingStrategy.java | 11 +++++ .../visitors/diff/MapPairingStrategy.java | 11 +++++ .../base/visitors/diff/PairingStrategy.java | 45 ++--------------- .../io/apitomy/umg/full/expected-manifest.txt | 2 + .../visitors/diff/AbstractDiffTraverser.java | 10 ++-- .../visitors/diff/CollectionDiff.java | 35 +++++++++++++ .../synthetic/visitors/diff/DiffVisitor.java | 17 ++++--- .../visitors/diff/IndexPairingStrategy.java | 14 ++---- .../visitors/diff/KeyPairingStrategy.java | 6 +-- .../visitors/diff/ListPairingStrategy.java | 11 +++++ .../visitors/diff/MapPairingStrategy.java | 11 +++++ .../visitors/diff/PairingStrategy.java | 49 ++----------------- 18 files changed, 177 insertions(+), 136 deletions(-) create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/ListPairingStrategy.java create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/MapPairingStrategy.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/ListPairingStrategy.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/MapPairingStrategy.java diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java index e973a927b..ee94781d0 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java @@ -69,6 +69,8 @@ protected void doProcess() { "io.apitomy.umg.base.union.MapUnionValue", "io.apitomy.umg.base.union.PrimitiveUnionValue", "io.apitomy.umg.base.union.StringListUnionValue", + "io.apitomy.umg.base.visitors.diff.ListPairingStrategy", + "io.apitomy.umg.base.visitors.diff.MapPairingStrategy", "io.apitomy.umg.base.visitors.diff.PairingStrategy", "io.apitomy.umg.base.union.Union", "io.apitomy.umg.base.union.UnionValue" diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java index 77ec62d84..430acaf1c 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -6,7 +6,7 @@ import io.apitomy.umg.base.Node; /** - * Base class for generated diff traversers. Provides shared collection diffing logic. + * Base class for generated diff traversers. Provides shared collection pairing logic. * Subclasses (generated per spec version) provide entity-specific field iteration * and call typed visitor methods. * @@ -22,26 +22,22 @@ protected AbstractDiffTraverser(V visitor) { @SuppressWarnings("unchecked") protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { - PairingStrategy strategy = - (PairingStrategy) visitor.getPairingStrategy(propertyName); + MapPairingStrategy strategy = + (MapPairingStrategy) visitor.getMapPairingStrategy(propertyName); if (strategy == null) { strategy = new KeyPairingStrategy<>(); } - return strategy.pair( - original != null ? original : Map.of(), - updated != null ? updated : Map.of()); + return strategy.pair(original, updated); } @SuppressWarnings("unchecked") protected CollectionDiff pairList(String propertyName, List original, List updated) { - PairingStrategy strategy = - (PairingStrategy) visitor.getPairingStrategy(propertyName); + ListPairingStrategy strategy = + (ListPairingStrategy) visitor.getListPairingStrategy(propertyName); if (strategy == null) { strategy = new IndexPairingStrategy<>(); } - return strategy.pair( - IndexPairingStrategy.toIndexMap(original), - IndexPairingStrategy.toIndexMap(updated)); + return strategy.pair(original, updated); } /** diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/CollectionDiff.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/CollectionDiff.java index 8b96fd62e..3f84383fe 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/CollectionDiff.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/CollectionDiff.java @@ -1,7 +1,11 @@ package io.apitomy.umg.base.visitors.diff; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Result of pairing two collections. Contains entries that were added, removed, @@ -39,6 +43,37 @@ public boolean hasChanges() { return !added.isEmpty() || !removed.isEmpty(); } + /** + * Pairs two maps by key. Entries with matching keys are paired; + * keys in only one map are classified as added or removed. + */ + public static CollectionDiff pairByKey(Map original, Map updated) { + Map orig = original != null ? original : new LinkedHashMap<>(); + Map upd = updated != null ? updated : new LinkedHashMap<>(); + + Set allKeys = new LinkedHashSet<>(); + allKeys.addAll(orig.keySet()); + allKeys.addAll(upd.keySet()); + + List> added = new ArrayList<>(); + List> removed = new ArrayList<>(); + List> matched = new ArrayList<>(); + + for (K key : allKeys) { + boolean inOrig = orig.containsKey(key); + boolean inUpd = upd.containsKey(key); + if (inOrig && inUpd) { + matched.add(new MatchedPair<>(key, orig.get(key), upd.get(key))); + } else if (inUpd) { + added.add(new Entry<>(key, upd.get(key))); + } else { + removed.add(new Entry<>(key, orig.get(key))); + } + } + + return new CollectionDiff<>(added, removed, matched); + } + public static class Entry { private final K key; private final V value; diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java index 15066b3bf..06b88ff6f 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java @@ -4,16 +4,22 @@ /** * Base class for generated per-spec-version DiffVisitors. Generated subclasses * add typed, field-specific methods for each entity and property. - * - *

The traverser auto-recurses into entity fields and matched collection pairs. */ public abstract class DiffVisitor { /** - * Returns the pairing strategy for a collection field, or null to use the default - * (key-based for maps, index-based for lists). + * Returns the map pairing strategy for a map field, or null to use the default + * (key-based pairing). */ - public PairingStrategy getPairingStrategy(String propertyName) { + public MapPairingStrategy getMapPairingStrategy(String propertyName) { + return null; + } + + /** + * Returns the list pairing strategy for a list field, or null to use the default + * (index-based pairing). + */ + public ListPairingStrategy getListPairingStrategy(String propertyName) { return null; } } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java index 4252aa9f8..fa4aa0477 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java @@ -5,20 +5,16 @@ import java.util.Map; /** - * Pairs list entries by index. Converts lists to index-keyed maps, - * then delegates to the shared pairing logic. + * Default list pairing strategy — pairs entries by index. */ -public class IndexPairingStrategy implements PairingStrategy { +public class IndexPairingStrategy implements ListPairingStrategy { @Override - public CollectionDiff pair(Map original, Map updated) { - return PairingStrategy.pairByKey(original, updated); + public CollectionDiff pair(List original, List updated) { + return CollectionDiff.pairByKey(toIndexMap(original), toIndexMap(updated)); } - /** - * Converts a list to an index-keyed map for use with this strategy. - */ - public static Map toIndexMap(List list) { + private static Map toIndexMap(List list) { if (list == null) return new LinkedHashMap<>(); Map map = new LinkedHashMap<>(); for (int i = 0; i < list.size(); i++) { diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java index bf5d0b629..e5bddca59 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java @@ -3,12 +3,12 @@ import java.util.Map; /** - * Pairs map entries by key. + * Default map pairing strategy — pairs entries by key. */ -public class KeyPairingStrategy implements PairingStrategy { +public class KeyPairingStrategy implements MapPairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { - return PairingStrategy.pairByKey(original, updated); + return CollectionDiff.pairByKey(original, updated); } } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/ListPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/ListPairingStrategy.java new file mode 100644 index 000000000..a0245ea60 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/ListPairingStrategy.java @@ -0,0 +1,11 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.List; + +/** + * Strategy for pairing entries from two lists. + */ +public interface ListPairingStrategy { + + CollectionDiff pair(List original, List updated); +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/MapPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/MapPairingStrategy.java new file mode 100644 index 000000000..13abf6401 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/MapPairingStrategy.java @@ -0,0 +1,11 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.Map; + +/** + * Strategy for pairing entries from two maps. + */ +public interface MapPairingStrategy { + + CollectionDiff pair(Map original, Map updated); +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java index 5d0fedebc..ab8293f91 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java @@ -1,46 +1,9 @@ package io.apitomy.umg.base.visitors.diff; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - /** - * Strategy for pairing elements from two collections to produce a {@link CollectionDiff}. - * - * @param the key type (String for maps, Integer for lists) - * @param the value type + * Marker interface for pairing strategies. + * @see MapPairingStrategy + * @see ListPairingStrategy */ -public interface PairingStrategy { - - CollectionDiff pair(Map original, Map updated); - - static CollectionDiff pairByKey(Map original, Map updated) { - Map orig = original != null ? original : new LinkedHashMap<>(); - Map upd = updated != null ? updated : new LinkedHashMap<>(); - - Set allKeys = new LinkedHashSet<>(); - allKeys.addAll(orig.keySet()); - allKeys.addAll(upd.keySet()); - - List> added = new ArrayList<>(); - List> removed = new ArrayList<>(); - List> matched = new ArrayList<>(); - - for (K key : allKeys) { - boolean inOrig = orig.containsKey(key); - boolean inUpd = upd.containsKey(key); - if (inOrig && inUpd) { - matched.add(new CollectionDiff.MatchedPair<>(key, orig.get(key), upd.get(key))); - } else if (inUpd) { - added.add(new CollectionDiff.Entry<>(key, upd.get(key))); - } else { - removed.add(new CollectionDiff.Entry<>(key, orig.get(key))); - } - } - - return new CollectionDiff<>(added, removed, matched); - } +public interface PairingStrategy { } diff --git a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt index 6789dba77..b5c1cac0f 100644 --- a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt +++ b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt @@ -1433,4 +1433,6 @@ io/apitomy/datamodels/models/visitors/diff/CollectionDiff.java io/apitomy/datamodels/models/visitors/diff/DiffVisitor.java io/apitomy/datamodels/models/visitors/diff/IndexPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/KeyPairingStrategy.java +io/apitomy/datamodels/models/visitors/diff/ListPairingStrategy.java +io/apitomy/datamodels/models/visitors/diff/MapPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/PairingStrategy.java diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java index 572f9d8f2..1c00c12ab 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java @@ -5,7 +5,7 @@ import java.util.Map; /** - * Base class for generated diff traversers. Provides shared collection diffing + * Base class for generated diff traversers. Provides shared collection pairing * logic. Subclasses (generated per spec version) provide entity-specific field * iteration and call typed visitor methods. * @@ -23,20 +23,20 @@ protected AbstractDiffTraverser(V visitor) { @SuppressWarnings("unchecked") protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { - PairingStrategy strategy = (PairingStrategy) visitor.getPairingStrategy(propertyName); + MapPairingStrategy strategy = (MapPairingStrategy) visitor.getMapPairingStrategy(propertyName); if (strategy == null) { strategy = new KeyPairingStrategy<>(); } - return strategy.pair(original != null ? original : Map.of(), updated != null ? updated : Map.of()); + return strategy.pair(original, updated); } @SuppressWarnings("unchecked") protected CollectionDiff pairList(String propertyName, List original, List updated) { - PairingStrategy strategy = (PairingStrategy) visitor.getPairingStrategy(propertyName); + ListPairingStrategy strategy = (ListPairingStrategy) visitor.getListPairingStrategy(propertyName); if (strategy == null) { strategy = new IndexPairingStrategy<>(); } - return strategy.pair(IndexPairingStrategy.toIndexMap(original), IndexPairingStrategy.toIndexMap(updated)); + return strategy.pair(original, updated); } /** diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/CollectionDiff.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/CollectionDiff.java index 36640b45d..a68eca8f4 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/CollectionDiff.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/CollectionDiff.java @@ -1,7 +1,11 @@ package io.test.synthetic.visitors.diff; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Result of pairing two collections. Contains entries that were added, removed, @@ -40,6 +44,37 @@ public boolean hasChanges() { return !added.isEmpty() || !removed.isEmpty(); } + /** + * Pairs two maps by key. Entries with matching keys are paired; keys in only + * one map are classified as added or removed. + */ + public static CollectionDiff pairByKey(Map original, Map updated) { + Map orig = original != null ? original : new LinkedHashMap<>(); + Map upd = updated != null ? updated : new LinkedHashMap<>(); + + Set allKeys = new LinkedHashSet<>(); + allKeys.addAll(orig.keySet()); + allKeys.addAll(upd.keySet()); + + List> added = new ArrayList<>(); + List> removed = new ArrayList<>(); + List> matched = new ArrayList<>(); + + for (K key : allKeys) { + boolean inOrig = orig.containsKey(key); + boolean inUpd = upd.containsKey(key); + if (inOrig && inUpd) { + matched.add(new MatchedPair<>(key, orig.get(key), upd.get(key))); + } else if (inUpd) { + added.add(new Entry<>(key, upd.get(key))); + } else { + removed.add(new Entry<>(key, orig.get(key))); + } + } + + return new CollectionDiff<>(added, removed, matched); + } + public static class Entry { private final K key; private final V value; diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java index 0332166a8..98f05e0ac 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java @@ -3,17 +3,22 @@ /** * Base class for generated per-spec-version DiffVisitors. Generated subclasses * add typed, field-specific methods for each entity and property. - * - *

- * The traverser auto-recurses into entity fields and matched collection pairs. */ public abstract class DiffVisitor { /** - * Returns the pairing strategy for a collection field, or null to use the - * default (key-based for maps, index-based for lists). + * Returns the map pairing strategy for a map field, or null to use the default + * (key-based pairing). */ - public PairingStrategy getPairingStrategy(String propertyName) { + public MapPairingStrategy getMapPairingStrategy(String propertyName) { + return null; + } + + /** + * Returns the list pairing strategy for a list field, or null to use the + * default (index-based pairing). + */ + public ListPairingStrategy getListPairingStrategy(String propertyName) { return null; } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java index 66cba0d4b..c680453e6 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java @@ -5,20 +5,16 @@ import java.util.Map; /** - * Pairs list entries by index. Converts lists to index-keyed maps, then - * delegates to the shared pairing logic. + * Default list pairing strategy — pairs entries by index. */ -public class IndexPairingStrategy implements PairingStrategy { +public class IndexPairingStrategy implements ListPairingStrategy { @Override - public CollectionDiff pair(Map original, Map updated) { - return PairingStrategy.pairByKey(original, updated); + public CollectionDiff pair(List original, List updated) { + return CollectionDiff.pairByKey(toIndexMap(original), toIndexMap(updated)); } - /** - * Converts a list to an index-keyed map for use with this strategy. - */ - public static Map toIndexMap(List list) { + private static Map toIndexMap(List list) { if (list == null) return new LinkedHashMap<>(); Map map = new LinkedHashMap<>(); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java index 9f4b5b122..16d865722 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java @@ -3,12 +3,12 @@ import java.util.Map; /** - * Pairs map entries by key. + * Default map pairing strategy — pairs entries by key. */ -public class KeyPairingStrategy implements PairingStrategy { +public class KeyPairingStrategy implements MapPairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { - return PairingStrategy.pairByKey(original, updated); + return CollectionDiff.pairByKey(original, updated); } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/ListPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/ListPairingStrategy.java new file mode 100644 index 000000000..9a5494376 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/ListPairingStrategy.java @@ -0,0 +1,11 @@ +package io.test.synthetic.visitors.diff; + +import java.util.List; + +/** + * Strategy for pairing entries from two lists. + */ +public interface ListPairingStrategy { + + CollectionDiff pair(List original, List updated); +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/MapPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/MapPairingStrategy.java new file mode 100644 index 000000000..ecb3b3796 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/MapPairingStrategy.java @@ -0,0 +1,11 @@ +package io.test.synthetic.visitors.diff; + +import java.util.Map; + +/** + * Strategy for pairing entries from two maps. + */ +public interface MapPairingStrategy { + + CollectionDiff pair(Map original, Map updated); +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java index 87082d2d1..a709c7aa3 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java @@ -1,49 +1,10 @@ package io.test.synthetic.visitors.diff; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - /** - * Strategy for pairing elements from two collections to produce a - * {@link CollectionDiff}. - * - * @param - * the key type (String for maps, Integer for lists) - * @param - * the value type + * Marker interface for pairing strategies. + * + * @see MapPairingStrategy + * @see ListPairingStrategy */ -public interface PairingStrategy { - - CollectionDiff pair(Map original, Map updated); - - static CollectionDiff pairByKey(Map original, Map updated) { - Map orig = original != null ? original : new LinkedHashMap<>(); - Map upd = updated != null ? updated : new LinkedHashMap<>(); - - Set allKeys = new LinkedHashSet<>(); - allKeys.addAll(orig.keySet()); - allKeys.addAll(upd.keySet()); - - List> added = new ArrayList<>(); - List> removed = new ArrayList<>(); - List> matched = new ArrayList<>(); - - for (K key : allKeys) { - boolean inOrig = orig.containsKey(key); - boolean inUpd = upd.containsKey(key); - if (inOrig && inUpd) { - matched.add(new CollectionDiff.MatchedPair<>(key, orig.get(key), upd.get(key))); - } else if (inUpd) { - added.add(new CollectionDiff.Entry<>(key, upd.get(key))); - } else { - removed.add(new CollectionDiff.Entry<>(key, orig.get(key))); - } - } - - return new CollectionDiff<>(added, removed, matched); - } +public interface PairingStrategy { } From 9394bc8c08b8b7096d29b2d7bdd584a2b3f5e8ba Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 14:59:57 +0200 Subject: [PATCH 05/16] refactor: remove PairingStrategy marker, add P generic to strategy interfaces (C36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete PairingStrategy marker interface (unused) - MapPairingStrategy — P is the pairing key type (String for default) - ListPairingStrategy — P is the pairing key type (Integer for default) - Custom strategies can use any P that's meaningful to the visitor --- .../io/apitomy/umg/pipe/java/LoadBaseClassesStage.java | 2 +- .../umg/base/visitors/diff/AbstractDiffTraverser.java | 8 ++++---- .../io/apitomy/umg/base/visitors/diff/DiffVisitor.java | 4 ++-- .../umg/base/visitors/diff/IndexPairingStrategy.java | 2 +- .../umg/base/visitors/diff/KeyPairingStrategy.java | 2 +- .../umg/base/visitors/diff/ListPairingStrategy.java | 7 +++++-- .../umg/base/visitors/diff/MapPairingStrategy.java | 7 +++++-- .../umg/base/visitors/diff/PairingStrategy.java | 9 --------- .../io/apitomy/umg/full/expected-manifest.txt | 1 - .../synthetic/visitors/diff/AbstractDiffTraverser.java | 6 ++++-- .../io/test/synthetic/visitors/diff/DiffVisitor.java | 4 ++-- .../synthetic/visitors/diff/IndexPairingStrategy.java | 2 +- .../synthetic/visitors/diff/KeyPairingStrategy.java | 2 +- .../synthetic/visitors/diff/ListPairingStrategy.java | 9 +++++++-- .../synthetic/visitors/diff/MapPairingStrategy.java | 9 +++++++-- .../test/synthetic/visitors/diff/PairingStrategy.java | 10 ---------- 16 files changed, 41 insertions(+), 43 deletions(-) delete mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java delete mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java index ee94781d0..e59e10ea0 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java @@ -71,7 +71,7 @@ protected void doProcess() { "io.apitomy.umg.base.union.StringListUnionValue", "io.apitomy.umg.base.visitors.diff.ListPairingStrategy", "io.apitomy.umg.base.visitors.diff.MapPairingStrategy", - "io.apitomy.umg.base.visitors.diff.PairingStrategy", + "io.apitomy.umg.base.union.Union", "io.apitomy.umg.base.union.UnionValue" ); diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java index 430acaf1c..b7c6547b0 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -22,8 +22,8 @@ protected AbstractDiffTraverser(V visitor) { @SuppressWarnings("unchecked") protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { - MapPairingStrategy strategy = - (MapPairingStrategy) visitor.getMapPairingStrategy(propertyName); + MapPairingStrategy strategy = + (MapPairingStrategy) visitor.getMapPairingStrategy(propertyName); if (strategy == null) { strategy = new KeyPairingStrategy<>(); } @@ -32,8 +32,8 @@ protected CollectionDiff pairMap(String propertyName, Map CollectionDiff pairList(String propertyName, List original, List updated) { - ListPairingStrategy strategy = - (ListPairingStrategy) visitor.getListPairingStrategy(propertyName); + ListPairingStrategy strategy = + (ListPairingStrategy) visitor.getListPairingStrategy(propertyName); if (strategy == null) { strategy = new IndexPairingStrategy<>(); } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java index 06b88ff6f..49fa36034 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java @@ -11,7 +11,7 @@ public abstract class DiffVisitor { * Returns the map pairing strategy for a map field, or null to use the default * (key-based pairing). */ - public MapPairingStrategy getMapPairingStrategy(String propertyName) { + public MapPairingStrategy getMapPairingStrategy(String propertyName) { return null; } @@ -19,7 +19,7 @@ public MapPairingStrategy getMapPairingStrategy(String propertyName) { * Returns the list pairing strategy for a list field, or null to use the default * (index-based pairing). */ - public ListPairingStrategy getListPairingStrategy(String propertyName) { + public ListPairingStrategy getListPairingStrategy(String propertyName) { return null; } } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java index fa4aa0477..448137f17 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java @@ -7,7 +7,7 @@ /** * Default list pairing strategy — pairs entries by index. */ -public class IndexPairingStrategy implements ListPairingStrategy { +public class IndexPairingStrategy implements ListPairingStrategy { @Override public CollectionDiff pair(List original, List updated) { diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java index e5bddca59..37234fcf5 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java @@ -5,7 +5,7 @@ /** * Default map pairing strategy — pairs entries by key. */ -public class KeyPairingStrategy implements MapPairingStrategy { +public class KeyPairingStrategy implements MapPairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/ListPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/ListPairingStrategy.java index a0245ea60..ae0b7f1a9 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/ListPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/ListPairingStrategy.java @@ -4,8 +4,11 @@ /** * Strategy for pairing entries from two lists. + * + * @param

the pairing key type (e.g., Integer for index-based pairing) + * @param the value type */ -public interface ListPairingStrategy { +public interface ListPairingStrategy { - CollectionDiff pair(List original, List updated); + CollectionDiff pair(List original, List updated); } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/MapPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/MapPairingStrategy.java index 13abf6401..1a16710d5 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/MapPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/MapPairingStrategy.java @@ -4,8 +4,11 @@ /** * Strategy for pairing entries from two maps. + * + * @param

the pairing key type (e.g., String for key-based pairing) + * @param the value type */ -public interface MapPairingStrategy { +public interface MapPairingStrategy { - CollectionDiff pair(Map original, Map updated); + CollectionDiff pair(Map original, Map updated); } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java deleted file mode 100644 index ab8293f91..000000000 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategy.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.apitomy.umg.base.visitors.diff; - -/** - * Marker interface for pairing strategies. - * @see MapPairingStrategy - * @see ListPairingStrategy - */ -public interface PairingStrategy { -} diff --git a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt index b5c1cac0f..0203e1460 100644 --- a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt +++ b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt @@ -1435,4 +1435,3 @@ io/apitomy/datamodels/models/visitors/diff/IndexPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/KeyPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/ListPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/MapPairingStrategy.java -io/apitomy/datamodels/models/visitors/diff/PairingStrategy.java diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java index 1c00c12ab..30f78c003 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java @@ -23,7 +23,8 @@ protected AbstractDiffTraverser(V visitor) { @SuppressWarnings("unchecked") protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { - MapPairingStrategy strategy = (MapPairingStrategy) visitor.getMapPairingStrategy(propertyName); + MapPairingStrategy strategy = (MapPairingStrategy) visitor + .getMapPairingStrategy(propertyName); if (strategy == null) { strategy = new KeyPairingStrategy<>(); } @@ -32,7 +33,8 @@ protected CollectionDiff pairMap(String propertyName, Map CollectionDiff pairList(String propertyName, List original, List updated) { - ListPairingStrategy strategy = (ListPairingStrategy) visitor.getListPairingStrategy(propertyName); + ListPairingStrategy strategy = (ListPairingStrategy) visitor + .getListPairingStrategy(propertyName); if (strategy == null) { strategy = new IndexPairingStrategy<>(); } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java index 98f05e0ac..a2604919f 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java @@ -10,7 +10,7 @@ public abstract class DiffVisitor { * Returns the map pairing strategy for a map field, or null to use the default * (key-based pairing). */ - public MapPairingStrategy getMapPairingStrategy(String propertyName) { + public MapPairingStrategy getMapPairingStrategy(String propertyName) { return null; } @@ -18,7 +18,7 @@ public MapPairingStrategy getMapPairingStrategy(String propertyName) { * Returns the list pairing strategy for a list field, or null to use the * default (index-based pairing). */ - public ListPairingStrategy getListPairingStrategy(String propertyName) { + public ListPairingStrategy getListPairingStrategy(String propertyName) { return null; } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java index c680453e6..a9aff073c 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java @@ -7,7 +7,7 @@ /** * Default list pairing strategy — pairs entries by index. */ -public class IndexPairingStrategy implements ListPairingStrategy { +public class IndexPairingStrategy implements ListPairingStrategy { @Override public CollectionDiff pair(List original, List updated) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java index 16d865722..652245530 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java @@ -5,7 +5,7 @@ /** * Default map pairing strategy — pairs entries by key. */ -public class KeyPairingStrategy implements MapPairingStrategy { +public class KeyPairingStrategy implements MapPairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/ListPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/ListPairingStrategy.java index 9a5494376..8bd8b609b 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/ListPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/ListPairingStrategy.java @@ -4,8 +4,13 @@ /** * Strategy for pairing entries from two lists. + * + * @param

+ * the pairing key type (e.g., Integer for index-based pairing) + * @param + * the value type */ -public interface ListPairingStrategy { +public interface ListPairingStrategy { - CollectionDiff pair(List original, List updated); + CollectionDiff pair(List original, List updated); } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/MapPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/MapPairingStrategy.java index ecb3b3796..457081a49 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/MapPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/MapPairingStrategy.java @@ -4,8 +4,13 @@ /** * Strategy for pairing entries from two maps. + * + * @param

+ * the pairing key type (e.g., String for key-based pairing) + * @param + * the value type */ -public interface MapPairingStrategy { +public interface MapPairingStrategy { - CollectionDiff pair(Map original, Map updated); + CollectionDiff pair(Map original, Map updated); } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java deleted file mode 100644 index a709c7aa3..000000000 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategy.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.test.synthetic.visitors.diff; - -/** - * Marker interface for pairing strategies. - * - * @see MapPairingStrategy - * @see ListPairingStrategy - */ -public interface PairingStrategy { -} From 798fc9dbb282649993aed394941a6ca7f32318e6 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 15:18:50 +0200 Subject: [PATCH 06/16] refactor: remove key/index params from item visitor methods (C36) Item visitor methods no longer receive the pairing key since it's strategy-dependent and not necessarily the map key or list index. Visitors that need the key context get it from the CollectionDiff in the collection-level diff method. Before: visitFullSchema$defs(String key, JsonSchema original, JsonSchema updated) After: visitFullSchema$defs(JsonSchema original, JsonSchema updated) --- .../umg/pipe/java/CreateDiffTraversersStage.java | 4 ++-- .../umg/pipe/java/CreateDiffVisitorsStage.java | 7 +------ .../synthetic/v1/visitors/Syn1DiffTraverser.java | 14 +++++++------- .../synthetic/v1/visitors/Syn1DiffVisitor.java | 14 +++++++------- .../synthetic/v2/visitors/Syn2DiffTraverser.java | 16 ++++++++-------- .../synthetic/v2/visitors/Syn2DiffVisitor.java | 16 ++++++++-------- 6 files changed, 33 insertions(+), 38 deletions(-) diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index 7588458fa..0b18da67a 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -194,7 +194,7 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.append(" CollectionDiff diff = this.pairList(\"${propertyName}\", original.${getter}(), updated.${getter}());"); body.append(" visitor.${diffMethod}(diff);"); body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); - body.append(" visitor.${visitMethod2}(pair.getKey(), pair.getOriginal(), pair.getUpdated());"); + body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityList(property)) { body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); body.append(" traverseNode(pair.getOriginal(), pair.getUpdated());"); @@ -217,7 +217,7 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.append(" CollectionDiff diff = this.pairMap(\"${propertyName}\", original.${getter}(), updated.${getter}());"); body.append(" visitor.${diffMethod}(diff);"); body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); - body.append(" visitor.${visitMethod2}(pair.getKey(), pair.getOriginal(), pair.getUpdated());"); + body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityMap(property)) { body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); body.append(" traverseNode(pair.getOriginal(), pair.getUpdated());"); diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java index 74eb8ddd3..6bf5e9c86 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java @@ -172,13 +172,11 @@ private void createListFieldMethods(JavaClassSource classSource, String entityNa diffMethod.addParameter("CollectionDiff", "diff"); diffMethod.setBody(""); - // visit method: visitEntityFieldItem(Integer index, ValueType original, ValueType updated) String visitMethodName = "visit" + entityName + fieldSuffix + "Item"; MethodSource visitMethod = classSource.addMethod() .setName(visitMethodName) .setReturnTypeVoid() .setPublic(); - visitMethod.addParameter("Integer", "index"); visitMethod.addParameter(valueJt.toJavaTypeString(), "original"); visitMethod.addParameter(valueJt.toJavaTypeString(), "updated"); visitMethod.setBody(""); @@ -203,14 +201,11 @@ private void createMapFieldMethods(JavaClassSource classSource, String entityNam diffMethod.addParameter("CollectionDiff", "diff"); diffMethod.setBody(""); - // visit method: visitEntityField(String key, ValueType original, ValueType updated) - String singularFieldSuffix = fieldSuffix; - String visitMethodName = "visit" + entityName + singularFieldSuffix; + String visitMethodName = "visit" + entityName + fieldSuffix; MethodSource visitMethod = classSource.addMethod() .setName(visitMethodName) .setReturnTypeVoid() .setPublic(); - visitMethod.addParameter("String", "key"); visitMethod.addParameter(valueJt.toJavaTypeString(), "original"); visitMethod.addParameter(valueJt.toJavaTypeString(), "updated"); visitMethod.setBody(""); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index a125a23fa..18443cb66 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -68,7 +68,7 @@ public void traverseDocument(Syn1Document original, Syn1Document updated) { CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); visitor.diffDocumentItems(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitDocumentItemsItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); } @@ -144,7 +144,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { updated.getProperties()); visitor.diffSchemaProperties(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaProperties(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaProperties(pair.getOriginal(), pair.getUpdated()); } } { @@ -152,7 +152,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { updated.getAllOf()); visitor.diffSchemaAllOf(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaAllOfItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); } } { @@ -160,7 +160,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { updated.getDefinitions()); visitor.diffSchemaDefinitions(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaDefinitions(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaDefinitions(pair.getOriginal(), pair.getUpdated()); } } { @@ -168,7 +168,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { updated.getNestedSchemas()); visitor.diffSchemaNestedSchemas(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaNestedSchemas(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaNestedSchemas(pair.getOriginal(), pair.getUpdated()); } } { @@ -176,7 +176,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { original.getComposedSchemas(), updated.getComposedSchemas()); visitor.diffSchemaComposedSchemas(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaComposedSchemasItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); } } visitor.diffSchemaMinLength(original.getMinLength(), updated.getMinLength()); @@ -231,7 +231,7 @@ public void traverseOperation(Syn1Operation original, Syn1Operation updated) { updated.getParameters()); visitor.diffOperationParameters(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitOperationParametersItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java index e13caca84..88ce5db51 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java @@ -38,7 +38,7 @@ public void diffDocumentInfo(SynInfo original, SynInfo updated) { public void diffDocumentItems(CollectionDiff diff) { } - public void visitDocumentItemsItem(Integer index, SynItem original, SynItem updated) { + public void visitDocumentItemsItem(SynItem original, SynItem updated) { } public void diffDocumentTags(List original, List updated) { @@ -129,31 +129,31 @@ public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchema public void diffSchemaProperties(CollectionDiff diff) { } - public void visitSchemaProperties(String key, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaProperties(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } public void diffSchemaAllOf(CollectionDiff diff) { } - public void visitSchemaAllOfItem(Integer index, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } public void diffSchemaDefinitions(CollectionDiff diff) { } - public void visitSchemaDefinitions(String key, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaDefinitions(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } public void diffSchemaNestedSchemas(CollectionDiff diff) { } - public void visitSchemaNestedSchemas(String key, SchemaOrBoolean original, SchemaOrBoolean updated) { + public void visitSchemaNestedSchemas(SchemaOrBoolean original, SchemaOrBoolean updated) { } public void diffSchemaComposedSchemas(CollectionDiff diff) { } - public void visitSchemaComposedSchemasItem(Integer index, SchemaOrBoolean original, SchemaOrBoolean updated) { + public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) { } public void diffSchemaMinLength(Integer original, Integer updated) { @@ -204,6 +204,6 @@ public void diffOperationTags(List original, List updated) { public void diffOperationParameters(CollectionDiff diff) { } - public void visitOperationParametersItem(Integer index, SynItem original, SynItem updated) { + public void visitOperationParametersItem(SynItem original, SynItem updated) { } } \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index 2772930f7..f10fa908a 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -68,7 +68,7 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); visitor.diffDocumentItems(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitDocumentItemsItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); } @@ -81,7 +81,7 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { updated.getWebhooks()); visitor.diffDocumentWebhooks(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitDocumentWebhooks(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitDocumentWebhooks(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); } @@ -157,7 +157,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { updated.getProperties()); visitor.diffSchemaProperties(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaProperties(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaProperties(pair.getOriginal(), pair.getUpdated()); } } { @@ -165,7 +165,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { updated.getAllOf()); visitor.diffSchemaAllOf(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaAllOfItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); } } { @@ -173,7 +173,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { updated.getDefinitions()); visitor.diffSchemaDefinitions(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaDefinitions(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaDefinitions(pair.getOriginal(), pair.getUpdated()); } } { @@ -181,7 +181,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { updated.getNestedSchemas()); visitor.diffSchemaNestedSchemas(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaNestedSchemas(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaNestedSchemas(pair.getOriginal(), pair.getUpdated()); } } { @@ -189,7 +189,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { original.getComposedSchemas(), updated.getComposedSchemas()); visitor.diffSchemaComposedSchemas(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaComposedSchemasItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); } } visitor.diffSchemaMinLength(original.getMinLength(), updated.getMinLength()); @@ -248,7 +248,7 @@ public void traverseOperation(Syn2Operation original, Syn2Operation updated) { updated.getParameters()); visitor.diffOperationParameters(diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitOperationParametersItem(pair.getKey(), pair.getOriginal(), pair.getUpdated()); + visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java index 8809bdd2a..1c59aa059 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java @@ -38,7 +38,7 @@ public void diffDocumentInfo(SynInfo original, SynInfo updated) { public void diffDocumentItems(CollectionDiff diff) { } - public void visitDocumentItemsItem(Integer index, SynItem original, SynItem updated) { + public void visitDocumentItemsItem(SynItem original, SynItem updated) { } public void diffDocumentTags(List original, List updated) { @@ -50,7 +50,7 @@ public void diffDocumentMetadata(Map original, Map diff) { } - public void visitDocumentWebhooks(String key, Syn2PathItem original, Syn2PathItem updated) { + public void visitDocumentWebhooks(Syn2PathItem original, Syn2PathItem updated) { } public void diffDocumentAdditionalSchema(SchemaOrBoolean original, SchemaOrBoolean updated) { @@ -141,31 +141,31 @@ public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchema public void diffSchemaProperties(CollectionDiff diff) { } - public void visitSchemaProperties(String key, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaProperties(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } public void diffSchemaAllOf(CollectionDiff diff) { } - public void visitSchemaAllOfItem(Integer index, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } public void diffSchemaDefinitions(CollectionDiff diff) { } - public void visitSchemaDefinitions(String key, BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaDefinitions(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } public void diffSchemaNestedSchemas(CollectionDiff diff) { } - public void visitSchemaNestedSchemas(String key, SchemaOrBoolean original, SchemaOrBoolean updated) { + public void visitSchemaNestedSchemas(SchemaOrBoolean original, SchemaOrBoolean updated) { } public void diffSchemaComposedSchemas(CollectionDiff diff) { } - public void visitSchemaComposedSchemasItem(Integer index, SchemaOrBoolean original, SchemaOrBoolean updated) { + public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) { } public void diffSchemaMinLength(Integer original, Integer updated) { @@ -219,6 +219,6 @@ public void diffOperationTags(List original, List updated) { public void diffOperationParameters(CollectionDiff diff) { } - public void visitOperationParametersItem(Integer index, SynItem original, SynItem updated) { + public void visitOperationParametersItem(SynItem original, SynItem updated) { } } \ No newline at end of file From 14a6b1c6c9d00ad657c22bbb97316d61758135c1 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 15:29:00 +0200 Subject: [PATCH 07/16] refactor: extract PairingStrategyProvider, strip strategy from DiffVisitor (C36) Strategy selection is now a separate concern from comparison logic: - PairingStrategyProvider interface with getMapStrategy/getListStrategy - DefaultPairingStrategyProvider: key-based for maps, index-based for lists - AbstractDiffTraverser takes provider at construction (defaults to DefaultProvider) - Generated traversers expose two constructors: (visitor) and (visitor, provider) - DiffVisitor is now a pure base for field-specific comparison methods --- .../pipe/java/CreateDiffTraversersStage.java | 12 ++++++++++- .../umg/pipe/java/LoadBaseClassesStage.java | 2 ++ .../visitors/diff/AbstractDiffTraverser.java | 20 ++++++++----------- .../diff/DefaultPairingStrategyProvider.java | 17 ++++++++++++++++ .../umg/base/visitors/diff/DiffVisitor.java | 17 ---------------- .../diff/PairingStrategyProvider.java | 13 ++++++++++++ .../io/apitomy/umg/full/expected-manifest.txt | 2 ++ .../v1/visitors/Syn1DiffTraverser.java | 5 +++++ .../v2/visitors/Syn2DiffTraverser.java | 5 +++++ .../visitors/diff/AbstractDiffTraverser.java | 20 ++++++++----------- .../diff/DefaultPairingStrategyProvider.java | 17 ++++++++++++++++ .../synthetic/visitors/diff/DiffVisitor.java | 16 --------------- .../diff/PairingStrategyProvider.java | 13 ++++++++++++ 13 files changed, 101 insertions(+), 58 deletions(-) create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategyProvider.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategyProvider.java diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index 0b18da67a..14977e4b7 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -63,12 +63,22 @@ private void createDiffTraverser(SpecificationVersion specVer) { JavaInterfaceSource nodeSource = getState().getJavaIndex().lookupInterface(nodeFQN); classSource.addImport(nodeSource); - // Constructor + // Import PairingStrategyProvider + String providerFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.PairingStrategyProvider"; + classSource.addImport(providerFQN); + + // Constructors MethodSource constructor = classSource.addMethod() .setConstructor(true).setPublic(); constructor.addParameter(visitorClassName, "visitor"); constructor.setBody("super(visitor);"); + MethodSource constructor2 = classSource.addMethod() + .setConstructor(true).setPublic(); + constructor2.addParameter(visitorClassName, "visitor"); + constructor2.addParameter("PairingStrategyProvider", "pairingProvider"); + constructor2.setBody("super(visitor, pairingProvider);"); + java.util.Set createdMethods = new java.util.HashSet<>(); createTraverseNodeDispatch(specVer, classSource, createdMethods); diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java index e59e10ea0..c17fdf556 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java @@ -40,6 +40,7 @@ protected void doProcess() { "io.apitomy.umg.base.visitors.ReverseTraverser", "io.apitomy.umg.base.visitors.diff.AbstractDiffTraverser", "io.apitomy.umg.base.visitors.diff.CollectionDiff", + "io.apitomy.umg.base.visitors.diff.DefaultPairingStrategyProvider", "io.apitomy.umg.base.visitors.diff.DiffVisitor", "io.apitomy.umg.base.visitors.diff.IndexPairingStrategy", "io.apitomy.umg.base.visitors.diff.KeyPairingStrategy", @@ -71,6 +72,7 @@ protected void doProcess() { "io.apitomy.umg.base.union.StringListUnionValue", "io.apitomy.umg.base.visitors.diff.ListPairingStrategy", "io.apitomy.umg.base.visitors.diff.MapPairingStrategy", + "io.apitomy.umg.base.visitors.diff.PairingStrategyProvider", "io.apitomy.umg.base.union.Union", "io.apitomy.umg.base.union.UnionValue" diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java index b7c6547b0..8910c846e 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -15,28 +15,24 @@ public class AbstractDiffTraverser { protected final V visitor; + protected final PairingStrategyProvider pairingProvider; protected AbstractDiffTraverser(V visitor) { + this(visitor, new DefaultPairingStrategyProvider()); + } + + protected AbstractDiffTraverser(V visitor, PairingStrategyProvider pairingProvider) { this.visitor = visitor; + this.pairingProvider = pairingProvider; } - @SuppressWarnings("unchecked") protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { - MapPairingStrategy strategy = - (MapPairingStrategy) visitor.getMapPairingStrategy(propertyName); - if (strategy == null) { - strategy = new KeyPairingStrategy<>(); - } + MapPairingStrategy strategy = pairingProvider.getMapStrategy(propertyName); return strategy.pair(original, updated); } - @SuppressWarnings("unchecked") protected CollectionDiff pairList(String propertyName, List original, List updated) { - ListPairingStrategy strategy = - (ListPairingStrategy) visitor.getListPairingStrategy(propertyName); - if (strategy == null) { - strategy = new IndexPairingStrategy<>(); - } + ListPairingStrategy strategy = pairingProvider.getListStrategy(propertyName); return strategy.pair(original, updated); } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java new file mode 100644 index 000000000..b5cc39f99 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java @@ -0,0 +1,17 @@ +package io.apitomy.umg.base.visitors.diff; + +/** + * Default provider: key-based pairing for maps, index-based for lists. + */ +public class DefaultPairingStrategyProvider implements PairingStrategyProvider { + + @Override + public MapPairingStrategy getMapStrategy(String propertyName) { + return new KeyPairingStrategy<>(); + } + + @Override + public ListPairingStrategy getListStrategy(String propertyName) { + return new IndexPairingStrategy<>(); + } +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java index 49fa36034..ab5c011cd 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java @@ -1,25 +1,8 @@ package io.apitomy.umg.base.visitors.diff; - /** * Base class for generated per-spec-version DiffVisitors. Generated subclasses * add typed, field-specific methods for each entity and property. */ public abstract class DiffVisitor { - - /** - * Returns the map pairing strategy for a map field, or null to use the default - * (key-based pairing). - */ - public MapPairingStrategy getMapPairingStrategy(String propertyName) { - return null; - } - - /** - * Returns the list pairing strategy for a list field, or null to use the default - * (index-based pairing). - */ - public ListPairingStrategy getListPairingStrategy(String propertyName) { - return null; - } } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategyProvider.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategyProvider.java new file mode 100644 index 000000000..9ba3a01a3 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategyProvider.java @@ -0,0 +1,13 @@ +package io.apitomy.umg.base.visitors.diff; + +/** + * Provides pairing strategies for collection fields during diff traversal. + * Pass a custom provider to the DiffTraverser constructor to control how + * map and list fields are paired. + */ +public interface PairingStrategyProvider { + + MapPairingStrategy getMapStrategy(String propertyName); + + ListPairingStrategy getListStrategy(String propertyName); +} diff --git a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt index 0203e1460..a98160889 100644 --- a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt +++ b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt @@ -1430,8 +1430,10 @@ io/apitomy/datamodels/models/visitors/TraversingVisitor.java io/apitomy/datamodels/models/visitors/Visitor.java io/apitomy/datamodels/models/visitors/diff/AbstractDiffTraverser.java io/apitomy/datamodels/models/visitors/diff/CollectionDiff.java +io/apitomy/datamodels/models/visitors/diff/DefaultPairingStrategyProvider.java io/apitomy/datamodels/models/visitors/diff/DiffVisitor.java io/apitomy/datamodels/models/visitors/diff/IndexPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/KeyPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/ListPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/MapPairingStrategy.java +io/apitomy/datamodels/models/visitors/diff/PairingStrategyProvider.java diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index 18443cb66..391e0c578 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -21,6 +21,7 @@ import io.test.synthetic.v1.Syn1Schema; import io.test.synthetic.visitors.diff.AbstractDiffTraverser; import io.test.synthetic.visitors.diff.CollectionDiff; +import io.test.synthetic.visitors.diff.PairingStrategyProvider; import java.util.List; import java.util.Map; @@ -30,6 +31,10 @@ public Syn1DiffTraverser(Syn1DiffVisitor visitor) { super(visitor); } + public Syn1DiffTraverser(Syn1DiffVisitor visitor, PairingStrategyProvider pairingProvider) { + super(visitor, pairingProvider); + } + @Override protected void traverseNode(Node original, Node updated) { Node target = original != null ? original : updated; diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index f10fa908a..83e4b9f16 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -21,6 +21,7 @@ import io.test.synthetic.v2.Syn2Schema; import io.test.synthetic.visitors.diff.AbstractDiffTraverser; import io.test.synthetic.visitors.diff.CollectionDiff; +import io.test.synthetic.visitors.diff.PairingStrategyProvider; import java.util.List; import java.util.Map; @@ -30,6 +31,10 @@ public Syn2DiffTraverser(Syn2DiffVisitor visitor) { super(visitor); } + public Syn2DiffTraverser(Syn2DiffVisitor visitor, PairingStrategyProvider pairingProvider) { + super(visitor, pairingProvider); + } + @Override protected void traverseNode(Node original, Node updated) { Node target = original != null ? original : updated; diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java index 30f78c003..c3558a79e 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java @@ -15,29 +15,25 @@ public class AbstractDiffTraverser { protected final V visitor; + protected final PairingStrategyProvider pairingProvider; protected AbstractDiffTraverser(V visitor) { + this(visitor, new DefaultPairingStrategyProvider()); + } + + protected AbstractDiffTraverser(V visitor, PairingStrategyProvider pairingProvider) { this.visitor = visitor; + this.pairingProvider = pairingProvider; } - @SuppressWarnings("unchecked") protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { - MapPairingStrategy strategy = (MapPairingStrategy) visitor - .getMapPairingStrategy(propertyName); - if (strategy == null) { - strategy = new KeyPairingStrategy<>(); - } + MapPairingStrategy strategy = pairingProvider.getMapStrategy(propertyName); return strategy.pair(original, updated); } - @SuppressWarnings("unchecked") protected CollectionDiff pairList(String propertyName, List original, List updated) { - ListPairingStrategy strategy = (ListPairingStrategy) visitor - .getListPairingStrategy(propertyName); - if (strategy == null) { - strategy = new IndexPairingStrategy<>(); - } + ListPairingStrategy strategy = pairingProvider.getListStrategy(propertyName); return strategy.pair(original, updated); } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java new file mode 100644 index 000000000..553ef9463 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java @@ -0,0 +1,17 @@ +package io.test.synthetic.visitors.diff; + +/** + * Default provider: key-based pairing for maps, index-based for lists. + */ +public class DefaultPairingStrategyProvider implements PairingStrategyProvider { + + @Override + public MapPairingStrategy getMapStrategy(String propertyName) { + return new KeyPairingStrategy<>(); + } + + @Override + public ListPairingStrategy getListStrategy(String propertyName) { + return new IndexPairingStrategy<>(); + } +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java index a2604919f..25c55f040 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java @@ -5,20 +5,4 @@ * add typed, field-specific methods for each entity and property. */ public abstract class DiffVisitor { - - /** - * Returns the map pairing strategy for a map field, or null to use the default - * (key-based pairing). - */ - public MapPairingStrategy getMapPairingStrategy(String propertyName) { - return null; - } - - /** - * Returns the list pairing strategy for a list field, or null to use the - * default (index-based pairing). - */ - public ListPairingStrategy getListPairingStrategy(String propertyName) { - return null; - } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategyProvider.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategyProvider.java new file mode 100644 index 000000000..24e4b6413 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategyProvider.java @@ -0,0 +1,13 @@ +package io.test.synthetic.visitors.diff; + +/** + * Provides pairing strategies for collection fields during diff traversal. Pass + * a custom provider to the DiffTraverser constructor to control how map and + * list fields are paired. + */ +public interface PairingStrategyProvider { + + MapPairingStrategy getMapStrategy(String propertyName); + + ListPairingStrategy getListStrategy(String propertyName); +} From 824e0d75fc194f7d82fd29a00f5d443001350b03 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 16:18:07 +0200 Subject: [PATCH 08/16] feat: thread P generic through diff infrastructure, add DefaultPairingKey (C36) - PairingStrategyProvider

with P for the pairing key type - DefaultPairingKey with index (int) and key (String) variants - DefaultPairingStrategyProvider implements PairingStrategyProvider - KeyPairingStrategy/IndexPairingStrategy produce CollectionDiff - AbstractDiffTraverser threads P through pairMap/pairList - Generated traversers use Object for P (erasure-safe, unchecked cast in default constructor) - Generated visitors use CollectionDiff for collection diff methods - DiffVisitor is now a pure marker base (strategy selection moved to PairingStrategyProvider) - Removed PairingStrategy marker interface --- .../pipe/java/CreateDiffTraversersStage.java | 12 ++--- .../pipe/java/CreateDiffVisitorsStage.java | 8 +-- .../umg/pipe/java/LoadBaseClassesStage.java | 1 + .../visitors/diff/AbstractDiffTraverser.java | 17 +++--- .../base/visitors/diff/DefaultPairingKey.java | 52 +++++++++++++++++++ .../diff/DefaultPairingStrategyProvider.java | 6 +-- .../visitors/diff/IndexPairingStrategy.java | 14 ++--- .../visitors/diff/KeyPairingStrategy.java | 17 ++++-- .../diff/PairingStrategyProvider.java | 6 +-- .../io/apitomy/umg/full/expected-manifest.txt | 1 + .../v1/visitors/Syn1DiffTraverser.java | 32 ++++++------ .../v1/visitors/Syn1DiffVisitor.java | 14 ++--- .../v2/visitors/Syn2DiffTraverser.java | 36 ++++++------- .../v2/visitors/Syn2DiffVisitor.java | 16 +++--- .../visitors/diff/AbstractDiffTraverser.java | 18 +++---- .../visitors/diff/DefaultPairingKey.java | 52 +++++++++++++++++++ .../diff/DefaultPairingStrategyProvider.java | 6 +-- .../visitors/diff/IndexPairingStrategy.java | 14 ++--- .../visitors/diff/KeyPairingStrategy.java | 18 +++++-- .../diff/PairingStrategyProvider.java | 6 +-- 20 files changed, 238 insertions(+), 108 deletions(-) create mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingKey.java create mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingKey.java diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index 14977e4b7..411d8f588 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -53,7 +53,7 @@ private void createDiffTraverser(SpecificationVersion specVer) { String baseFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.AbstractDiffTraverser"; JavaClassSource baseSource = getState().getJavaIndex().lookupClass(baseFQN); classSource.addImport(baseSource); - classSource.setSuperType("AbstractDiffTraverser<" + visitorClassName + ">"); + classSource.setSuperType("AbstractDiffTraverser"); // Import CollectionDiff for collection field handling String collectionDiffFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.CollectionDiff"; @@ -76,7 +76,7 @@ private void createDiffTraverser(SpecificationVersion specVer) { MethodSource constructor2 = classSource.addMethod() .setConstructor(true).setPublic(); constructor2.addParameter(visitorClassName, "visitor"); - constructor2.addParameter("PairingStrategyProvider", "pairingProvider"); + constructor2.addParameter("PairingStrategyProvider", "pairingProvider"); constructor2.setBody("super(visitor, pairingProvider);"); java.util.Set createdMethods = new java.util.HashSet<>(); @@ -201,9 +201,9 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.addContext("propertyName", property.getName()); body.append("{"); - body.append(" CollectionDiff diff = this.pairList(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + body.append(" CollectionDiff diff = this.pairList(\"${propertyName}\", original.${getter}(), updated.${getter}());"); body.append(" visitor.${diffMethod}(diff);"); - body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); + body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityList(property)) { body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); @@ -224,9 +224,9 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.addContext("propertyName", property.getName()); body.append("{"); - body.append(" CollectionDiff diff = this.pairMap(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + body.append(" CollectionDiff diff = this.pairMap(\"${propertyName}\", original.${getter}(), updated.${getter}());"); body.append(" visitor.${diffMethod}(diff);"); - body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); + body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityMap(property)) { body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java index 6bf5e9c86..47aae1355 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java @@ -163,13 +163,13 @@ private void createListFieldMethods(JavaClassSource classSource, String entityNa + ".visitors.diff.CollectionDiff"; classSource.addImport(collectionDiffFQN); - // diff method: diffEntityField(CollectionDiff diff) + // diff method: diffEntityField(CollectionDiff diff) String diffMethodName = "diff" + entityName + fieldSuffix; MethodSource diffMethod = classSource.addMethod() .setName(diffMethodName) .setReturnTypeVoid() .setPublic(); - diffMethod.addParameter("CollectionDiff", "diff"); + diffMethod.addParameter("CollectionDiff", "diff"); diffMethod.setBody(""); String visitMethodName = "visit" + entityName + fieldSuffix + "Item"; @@ -192,13 +192,13 @@ private void createMapFieldMethods(JavaClassSource classSource, String entityNam + ".visitors.diff.CollectionDiff"; classSource.addImport(collectionDiffFQN); - // diff method: diffEntityField(CollectionDiff diff) + // diff method: diffEntityField(CollectionDiff diff) String diffMethodName = "diff" + entityName + fieldSuffix; MethodSource diffMethod = classSource.addMethod() .setName(diffMethodName) .setReturnTypeVoid() .setPublic(); - diffMethod.addParameter("CollectionDiff", "diff"); + diffMethod.addParameter("CollectionDiff", "diff"); diffMethod.setBody(""); String visitMethodName = "visit" + entityName + fieldSuffix; diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java index c17fdf556..b0792420e 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java @@ -40,6 +40,7 @@ protected void doProcess() { "io.apitomy.umg.base.visitors.ReverseTraverser", "io.apitomy.umg.base.visitors.diff.AbstractDiffTraverser", "io.apitomy.umg.base.visitors.diff.CollectionDiff", + "io.apitomy.umg.base.visitors.diff.DefaultPairingKey", "io.apitomy.umg.base.visitors.diff.DefaultPairingStrategyProvider", "io.apitomy.umg.base.visitors.diff.DiffVisitor", "io.apitomy.umg.base.visitors.diff.IndexPairingStrategy", diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java index 8910c846e..7c7ca87fc 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -12,27 +12,28 @@ * * @param the spec-version-specific DiffVisitor subclass */ -public class AbstractDiffTraverser { +public class AbstractDiffTraverser { protected final V visitor; - protected final PairingStrategyProvider pairingProvider; + protected final PairingStrategyProvider

pairingProvider; + @SuppressWarnings("unchecked") protected AbstractDiffTraverser(V visitor) { - this(visitor, new DefaultPairingStrategyProvider()); + this(visitor, (PairingStrategyProvider

) (PairingStrategyProvider) new DefaultPairingStrategyProvider()); } - protected AbstractDiffTraverser(V visitor, PairingStrategyProvider pairingProvider) { + protected AbstractDiffTraverser(V visitor, PairingStrategyProvider

pairingProvider) { this.visitor = visitor; this.pairingProvider = pairingProvider; } - protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { - MapPairingStrategy strategy = pairingProvider.getMapStrategy(propertyName); + protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { + MapPairingStrategy strategy = pairingProvider.getMapStrategy(propertyName); return strategy.pair(original, updated); } - protected CollectionDiff pairList(String propertyName, List original, List updated) { - ListPairingStrategy strategy = pairingProvider.getListStrategy(propertyName); + protected CollectionDiff pairList(String propertyName, List original, List updated) { + ListPairingStrategy strategy = pairingProvider.getListStrategy(propertyName); return strategy.pair(original, updated); } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingKey.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingKey.java new file mode 100644 index 000000000..287d63ccc --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingKey.java @@ -0,0 +1,52 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.Objects; + +/** + * Default provider: key-based pairing for maps, index-based for lists. + */ +public class DefaultPairingKey { + + private Type type; + private Integer index; + private String key; + + public DefaultPairingKey(int index) { + type = Type.INDEX; + this.index = index; + } + + public DefaultPairingKey(String key) { + type = Type.KEY; + this.key = key; + } + + public Type getType() { + return type; + } + + public Integer getIndex() { + return index; + } + + public String getKey() { + return key; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof DefaultPairingKey)) return false; + DefaultPairingKey that = (DefaultPairingKey) o; + return type == that.type && Objects.equals(index, that.index) && Objects.equals(key, that.key); + } + + @Override + public int hashCode() { + return Objects.hash(type, index, key); + } + + public enum Type { + INDEX, + KEY + } +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java index b5cc39f99..757a60ca9 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java @@ -3,15 +3,15 @@ /** * Default provider: key-based pairing for maps, index-based for lists. */ -public class DefaultPairingStrategyProvider implements PairingStrategyProvider { +public class DefaultPairingStrategyProvider implements PairingStrategyProvider { @Override - public MapPairingStrategy getMapStrategy(String propertyName) { + public MapPairingStrategy getMapStrategy(String propertyName) { return new KeyPairingStrategy<>(); } @Override - public ListPairingStrategy getListStrategy(String propertyName) { + public ListPairingStrategy getListStrategy(String propertyName) { return new IndexPairingStrategy<>(); } } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java index 448137f17..00cf7714b 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java @@ -7,19 +7,19 @@ /** * Default list pairing strategy — pairs entries by index. */ -public class IndexPairingStrategy implements ListPairingStrategy { +public class IndexPairingStrategy implements ListPairingStrategy { @Override - public CollectionDiff pair(List original, List updated) { - return CollectionDiff.pairByKey(toIndexMap(original), toIndexMap(updated)); + public CollectionDiff pair(List original, List updated) { + return CollectionDiff.pairByKey(toPairingKeyMap(original), toPairingKeyMap(updated)); } - private static Map toIndexMap(List list) { + private static Map toPairingKeyMap(List list) { if (list == null) return new LinkedHashMap<>(); - Map map = new LinkedHashMap<>(); + Map res = new LinkedHashMap<>(); for (int i = 0; i < list.size(); i++) { - map.put(i, list.get(i)); + res.put(new DefaultPairingKey(i), list.get(i)); } - return map; + return res; } } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java index 37234fcf5..1a313bbb8 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java @@ -1,14 +1,25 @@ package io.apitomy.umg.base.visitors.diff; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** * Default map pairing strategy — pairs entries by key. */ -public class KeyPairingStrategy implements MapPairingStrategy { +public class KeyPairingStrategy implements MapPairingStrategy { @Override - public CollectionDiff pair(Map original, Map updated) { - return CollectionDiff.pairByKey(original, updated); + public CollectionDiff pair(Map original, Map updated) { + return CollectionDiff.pairByKey(toPairingKeyMap(original), toPairingKeyMap(updated)); + } + + private static Map toPairingKeyMap(Map map) { + if (map == null) return new LinkedHashMap<>(); + Map res = new LinkedHashMap<>(); + for (Map.Entry entry : map.entrySet()) { + res.put(new DefaultPairingKey(entry.getKey()), entry.getValue()); + } + return res; } } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategyProvider.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategyProvider.java index 9ba3a01a3..01e4a7dd6 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategyProvider.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/PairingStrategyProvider.java @@ -5,9 +5,9 @@ * Pass a custom provider to the DiffTraverser constructor to control how * map and list fields are paired. */ -public interface PairingStrategyProvider { +public interface PairingStrategyProvider

{ - MapPairingStrategy getMapStrategy(String propertyName); + MapPairingStrategy getMapStrategy(String propertyName); - ListPairingStrategy getListStrategy(String propertyName); + ListPairingStrategy getListStrategy(String propertyName); } diff --git a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt index a98160889..2d7617781 100644 --- a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt +++ b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt @@ -1430,6 +1430,7 @@ io/apitomy/datamodels/models/visitors/TraversingVisitor.java io/apitomy/datamodels/models/visitors/Visitor.java io/apitomy/datamodels/models/visitors/diff/AbstractDiffTraverser.java io/apitomy/datamodels/models/visitors/diff/CollectionDiff.java +io/apitomy/datamodels/models/visitors/diff/DefaultPairingKey.java io/apitomy/datamodels/models/visitors/diff/DefaultPairingStrategyProvider.java io/apitomy/datamodels/models/visitors/diff/DiffVisitor.java io/apitomy/datamodels/models/visitors/diff/IndexPairingStrategy.java diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index 391e0c578..909e474de 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -25,13 +25,13 @@ import java.util.List; import java.util.Map; -public class Syn1DiffTraverser extends AbstractDiffTraverser { +public class Syn1DiffTraverser extends AbstractDiffTraverser { public Syn1DiffTraverser(Syn1DiffVisitor visitor) { super(visitor); } - public Syn1DiffTraverser(Syn1DiffVisitor visitor, PairingStrategyProvider pairingProvider) { + public Syn1DiffTraverser(Syn1DiffVisitor visitor, PairingStrategyProvider pairingProvider) { super(visitor, pairingProvider); } @@ -70,9 +70,9 @@ public void traverseDocument(Syn1Document original, Syn1Document updated) { traverseNode(original.getInfo(), updated.getInfo()); } { - CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); + CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); visitor.diffDocumentItems(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); @@ -145,42 +145,42 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { visitor.diffSchemaType(original.getType(), updated.getType()); visitor.diffSchemaItems(original.getItems(), updated.getItems()); { - CollectionDiff diff = this.pairMap("properties", original.getProperties(), + CollectionDiff diff = this.pairMap("properties", original.getProperties(), updated.getProperties()); visitor.diffSchemaProperties(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaProperties(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairList("allOf", original.getAllOf(), + CollectionDiff diff = this.pairList("allOf", original.getAllOf(), updated.getAllOf()); visitor.diffSchemaAllOf(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), + CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), updated.getDefinitions()); visitor.diffSchemaDefinitions(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaDefinitions(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), + CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); visitor.diffSchemaNestedSchemas(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaNestedSchemas(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairList("composedSchemas", + CollectionDiff diff = this.pairList("composedSchemas", original.getComposedSchemas(), updated.getComposedSchemas()); visitor.diffSchemaComposedSchemas(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); } } @@ -232,10 +232,10 @@ public void traverseOperation(Syn1Operation original, Syn1Operation updated) { visitor.diffOperationSummary(original.getSummary(), updated.getSummary()); visitor.diffOperationTags(original.getTags(), updated.getTags()); { - CollectionDiff diff = this.pairList("parameters", original.getParameters(), + CollectionDiff diff = this.pairList("parameters", original.getParameters(), updated.getParameters()); visitor.diffOperationParameters(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java index 88ce5db51..8411c8ad5 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java @@ -35,7 +35,7 @@ public void diffDocumentVersion(String original, String updated) { public void diffDocumentInfo(SynInfo original, SynInfo updated) { } - public void diffDocumentItems(CollectionDiff diff) { + public void diffDocumentItems(CollectionDiff diff) { } public void visitDocumentItemsItem(SynItem original, SynItem updated) { @@ -126,31 +126,31 @@ public void diffSchemaType(String original, String updated) { public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchemaSchemaListUnion updated) { } - public void diffSchemaProperties(CollectionDiff diff) { + public void diffSchemaProperties(CollectionDiff diff) { } public void visitSchemaProperties(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaAllOf(CollectionDiff diff) { + public void diffSchemaAllOf(CollectionDiff diff) { } public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaDefinitions(CollectionDiff diff) { + public void diffSchemaDefinitions(CollectionDiff diff) { } public void visitSchemaDefinitions(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaNestedSchemas(CollectionDiff diff) { + public void diffSchemaNestedSchemas(CollectionDiff diff) { } public void visitSchemaNestedSchemas(SchemaOrBoolean original, SchemaOrBoolean updated) { } - public void diffSchemaComposedSchemas(CollectionDiff diff) { + public void diffSchemaComposedSchemas(CollectionDiff diff) { } public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) { @@ -201,7 +201,7 @@ public void diffOperationSummary(String original, String updated) { public void diffOperationTags(List original, List updated) { } - public void diffOperationParameters(CollectionDiff diff) { + public void diffOperationParameters(CollectionDiff diff) { } public void visitOperationParametersItem(SynItem original, SynItem updated) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index 83e4b9f16..82e36ca17 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -25,13 +25,13 @@ import java.util.List; import java.util.Map; -public class Syn2DiffTraverser extends AbstractDiffTraverser { +public class Syn2DiffTraverser extends AbstractDiffTraverser { public Syn2DiffTraverser(Syn2DiffVisitor visitor) { super(visitor); } - public Syn2DiffTraverser(Syn2DiffVisitor visitor, PairingStrategyProvider pairingProvider) { + public Syn2DiffTraverser(Syn2DiffVisitor visitor, PairingStrategyProvider pairingProvider) { super(visitor, pairingProvider); } @@ -70,9 +70,9 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { traverseNode(original.getInfo(), updated.getInfo()); } { - CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); + CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); visitor.diffDocumentItems(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); @@ -82,10 +82,10 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { visitor.diffDocumentTags(original.getTags(), updated.getTags()); visitor.diffDocumentMetadata(original.getMetadata(), updated.getMetadata()); { - CollectionDiff diff = this.pairMap("webhooks", original.getWebhooks(), + CollectionDiff diff = this.pairMap("webhooks", original.getWebhooks(), updated.getWebhooks()); visitor.diffDocumentWebhooks(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentWebhooks(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); @@ -158,42 +158,42 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { visitor.diffSchemaType(original.getType(), updated.getType()); visitor.diffSchemaItems(original.getItems(), updated.getItems()); { - CollectionDiff diff = this.pairMap("properties", original.getProperties(), + CollectionDiff diff = this.pairMap("properties", original.getProperties(), updated.getProperties()); visitor.diffSchemaProperties(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaProperties(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairList("allOf", original.getAllOf(), + CollectionDiff diff = this.pairList("allOf", original.getAllOf(), updated.getAllOf()); visitor.diffSchemaAllOf(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), + CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), updated.getDefinitions()); visitor.diffSchemaDefinitions(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaDefinitions(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), + CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); visitor.diffSchemaNestedSchemas(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaNestedSchemas(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairList("composedSchemas", + CollectionDiff diff = this.pairList("composedSchemas", original.getComposedSchemas(), updated.getComposedSchemas()); visitor.diffSchemaComposedSchemas(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); } } @@ -249,10 +249,10 @@ public void traverseOperation(Syn2Operation original, Syn2Operation updated) { visitor.diffOperationSummary(original.getSummary(), updated.getSummary()); visitor.diffOperationTags(original.getTags(), updated.getTags()); { - CollectionDiff diff = this.pairList("parameters", original.getParameters(), + CollectionDiff diff = this.pairList("parameters", original.getParameters(), updated.getParameters()); visitor.diffOperationParameters(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java index 1c59aa059..9862134ca 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java @@ -35,7 +35,7 @@ public void diffDocumentVersion(String original, String updated) { public void diffDocumentInfo(SynInfo original, SynInfo updated) { } - public void diffDocumentItems(CollectionDiff diff) { + public void diffDocumentItems(CollectionDiff diff) { } public void visitDocumentItemsItem(SynItem original, SynItem updated) { @@ -47,7 +47,7 @@ public void diffDocumentTags(List original, List updated) { public void diffDocumentMetadata(Map original, Map updated) { } - public void diffDocumentWebhooks(CollectionDiff diff) { + public void diffDocumentWebhooks(CollectionDiff diff) { } public void visitDocumentWebhooks(Syn2PathItem original, Syn2PathItem updated) { @@ -138,31 +138,31 @@ public void diffSchemaType(String original, String updated) { public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchemaSchemaListUnion updated) { } - public void diffSchemaProperties(CollectionDiff diff) { + public void diffSchemaProperties(CollectionDiff diff) { } public void visitSchemaProperties(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaAllOf(CollectionDiff diff) { + public void diffSchemaAllOf(CollectionDiff diff) { } public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaDefinitions(CollectionDiff diff) { + public void diffSchemaDefinitions(CollectionDiff diff) { } public void visitSchemaDefinitions(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaNestedSchemas(CollectionDiff diff) { + public void diffSchemaNestedSchemas(CollectionDiff diff) { } public void visitSchemaNestedSchemas(SchemaOrBoolean original, SchemaOrBoolean updated) { } - public void diffSchemaComposedSchemas(CollectionDiff diff) { + public void diffSchemaComposedSchemas(CollectionDiff diff) { } public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) { @@ -216,7 +216,7 @@ public void diffOperationSummary(String original, String updated) { public void diffOperationTags(List original, List updated) { } - public void diffOperationParameters(CollectionDiff diff) { + public void diffOperationParameters(CollectionDiff diff) { } public void visitOperationParametersItem(SynItem original, SynItem updated) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java index c3558a79e..e1feb1ed6 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java @@ -12,28 +12,28 @@ * @param * the spec-version-specific DiffVisitor subclass */ -public class AbstractDiffTraverser { +public class AbstractDiffTraverser { protected final V visitor; - protected final PairingStrategyProvider pairingProvider; + protected final PairingStrategyProvider

pairingProvider; + @SuppressWarnings("unchecked") protected AbstractDiffTraverser(V visitor) { - this(visitor, new DefaultPairingStrategyProvider()); + this(visitor, (PairingStrategyProvider

) (PairingStrategyProvider) new DefaultPairingStrategyProvider()); } - protected AbstractDiffTraverser(V visitor, PairingStrategyProvider pairingProvider) { + protected AbstractDiffTraverser(V visitor, PairingStrategyProvider

pairingProvider) { this.visitor = visitor; this.pairingProvider = pairingProvider; } - protected CollectionDiff pairMap(String propertyName, Map original, - Map updated) { - MapPairingStrategy strategy = pairingProvider.getMapStrategy(propertyName); + protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { + MapPairingStrategy strategy = pairingProvider.getMapStrategy(propertyName); return strategy.pair(original, updated); } - protected CollectionDiff pairList(String propertyName, List original, List updated) { - ListPairingStrategy strategy = pairingProvider.getListStrategy(propertyName); + protected CollectionDiff pairList(String propertyName, List original, List updated) { + ListPairingStrategy strategy = pairingProvider.getListStrategy(propertyName); return strategy.pair(original, updated); } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingKey.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingKey.java new file mode 100644 index 000000000..42208bc91 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingKey.java @@ -0,0 +1,52 @@ +package io.test.synthetic.visitors.diff; + +import java.util.Objects; + +/** + * Default provider: key-based pairing for maps, index-based for lists. + */ +public class DefaultPairingKey { + + private Type type; + private Integer index; + private String key; + + public DefaultPairingKey(int index) { + type = Type.INDEX; + this.index = index; + } + + public DefaultPairingKey(String key) { + type = Type.KEY; + this.key = key; + } + + public Type getType() { + return type; + } + + public Integer getIndex() { + return index; + } + + public String getKey() { + return key; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof DefaultPairingKey)) + return false; + DefaultPairingKey that = (DefaultPairingKey) o; + return type == that.type && Objects.equals(index, that.index) && Objects.equals(key, that.key); + } + + @Override + public int hashCode() { + return Objects.hash(type, index, key); + } + + public enum Type { + INDEX, KEY + } +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java index 553ef9463..be8276368 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java @@ -3,15 +3,15 @@ /** * Default provider: key-based pairing for maps, index-based for lists. */ -public class DefaultPairingStrategyProvider implements PairingStrategyProvider { +public class DefaultPairingStrategyProvider implements PairingStrategyProvider { @Override - public MapPairingStrategy getMapStrategy(String propertyName) { + public MapPairingStrategy getMapStrategy(String propertyName) { return new KeyPairingStrategy<>(); } @Override - public ListPairingStrategy getListStrategy(String propertyName) { + public ListPairingStrategy getListStrategy(String propertyName) { return new IndexPairingStrategy<>(); } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java index a9aff073c..d4c0f7c8c 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java @@ -7,20 +7,20 @@ /** * Default list pairing strategy — pairs entries by index. */ -public class IndexPairingStrategy implements ListPairingStrategy { +public class IndexPairingStrategy implements ListPairingStrategy { @Override - public CollectionDiff pair(List original, List updated) { - return CollectionDiff.pairByKey(toIndexMap(original), toIndexMap(updated)); + public CollectionDiff pair(List original, List updated) { + return CollectionDiff.pairByKey(toPairingKeyMap(original), toPairingKeyMap(updated)); } - private static Map toIndexMap(List list) { + private static Map toPairingKeyMap(List list) { if (list == null) return new LinkedHashMap<>(); - Map map = new LinkedHashMap<>(); + Map res = new LinkedHashMap<>(); for (int i = 0; i < list.size(); i++) { - map.put(i, list.get(i)); + res.put(new DefaultPairingKey(i), list.get(i)); } - return map; + return res; } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java index 652245530..589fb606d 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java @@ -1,14 +1,26 @@ package io.test.synthetic.visitors.diff; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** * Default map pairing strategy — pairs entries by key. */ -public class KeyPairingStrategy implements MapPairingStrategy { +public class KeyPairingStrategy implements MapPairingStrategy { @Override - public CollectionDiff pair(Map original, Map updated) { - return CollectionDiff.pairByKey(original, updated); + public CollectionDiff pair(Map original, Map updated) { + return CollectionDiff.pairByKey(toPairingKeyMap(original), toPairingKeyMap(updated)); + } + + private static Map toPairingKeyMap(Map map) { + if (map == null) + return new LinkedHashMap<>(); + Map res = new LinkedHashMap<>(); + for (Map.Entry entry : map.entrySet()) { + res.put(new DefaultPairingKey(entry.getKey()), entry.getValue()); + } + return res; } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategyProvider.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategyProvider.java index 24e4b6413..e33af1e1a 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategyProvider.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/PairingStrategyProvider.java @@ -5,9 +5,9 @@ * a custom provider to the DiffTraverser constructor to control how map and * list fields are paired. */ -public interface PairingStrategyProvider { +public interface PairingStrategyProvider

{ - MapPairingStrategy getMapStrategy(String propertyName); + MapPairingStrategy getMapStrategy(String propertyName); - ListPairingStrategy getListStrategy(String propertyName); + ListPairingStrategy getListStrategy(String propertyName); } From 177c47461ecf3b8a1c01ca8910bced91c13c4add Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 16:44:22 +0200 Subject: [PATCH 09/16] refactor: make generated DiffTraverser and DiffVisitor generic with

(C36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both generated classes are now parameterized by the pairing key type P: - JD6DiffTraverser

extends AbstractDiffTraverser> - JD6DiffVisitor

extends DiffVisitor CollectionDiff flows through the entire stack — no Object erasure or unchecked casts needed. Usage: new JD6DiffTraverser(visitor) --- .../pipe/java/CreateDiffTraversersStage.java | 15 +++---- .../pipe/java/CreateDiffVisitorsStage.java | 10 +++-- .../v1/visitors/Syn1DiffTraverser.java | 36 ++++++++--------- .../v1/visitors/Syn1DiffVisitor.java | 16 ++++---- .../v2/visitors/Syn2DiffTraverser.java | 40 +++++++++---------- .../v2/visitors/Syn2DiffVisitor.java | 18 ++++----- 6 files changed, 69 insertions(+), 66 deletions(-) diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index 411d8f588..d9ee6fa18 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -53,7 +53,8 @@ private void createDiffTraverser(SpecificationVersion specVer) { String baseFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.AbstractDiffTraverser"; JavaClassSource baseSource = getState().getJavaIndex().lookupClass(baseFQN); classSource.addImport(baseSource); - classSource.setSuperType("AbstractDiffTraverser"); + classSource.addTypeVariable("P"); + classSource.setSuperType("AbstractDiffTraverser>"); // Import CollectionDiff for collection field handling String collectionDiffFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.CollectionDiff"; @@ -70,13 +71,13 @@ private void createDiffTraverser(SpecificationVersion specVer) { // Constructors MethodSource constructor = classSource.addMethod() .setConstructor(true).setPublic(); - constructor.addParameter(visitorClassName, "visitor"); + constructor.addParameter(visitorClassName + "

", "visitor"); constructor.setBody("super(visitor);"); MethodSource constructor2 = classSource.addMethod() .setConstructor(true).setPublic(); constructor2.addParameter(visitorClassName, "visitor"); - constructor2.addParameter("PairingStrategyProvider", "pairingProvider"); + constructor2.addParameter("PairingStrategyProvider

", "pairingProvider"); constructor2.setBody("super(visitor, pairingProvider);"); java.util.Set createdMethods = new java.util.HashSet<>(); @@ -201,9 +202,9 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.addContext("propertyName", property.getName()); body.append("{"); - body.append(" CollectionDiff diff = this.pairList(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + body.append(" CollectionDiff diff = this.pairList(\"${propertyName}\", original.${getter}(), updated.${getter}());"); body.append(" visitor.${diffMethod}(diff);"); - body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); + body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityList(property)) { body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); @@ -224,9 +225,9 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.addContext("propertyName", property.getName()); body.append("{"); - body.append(" CollectionDiff diff = this.pairMap(\"${propertyName}\", original.${getter}(), updated.${getter}());"); + body.append(" CollectionDiff diff = this.pairMap(\"${propertyName}\", original.${getter}(), updated.${getter}());"); body.append(" visitor.${diffMethod}(diff);"); - body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); + body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityMap(property)) { body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java index 47aae1355..2aed39ba8 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java @@ -40,6 +40,8 @@ private void createDiffVisitor(SpecificationVersion specVer) { .setPublic() .setAbstract(true); + classSource.addTypeVariable("P"); + String baseFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.DiffVisitor"; JavaClassSource baseSource = getState().getJavaIndex().lookupClass(baseFQN); classSource.addImport(baseSource); @@ -163,13 +165,13 @@ private void createListFieldMethods(JavaClassSource classSource, String entityNa + ".visitors.diff.CollectionDiff"; classSource.addImport(collectionDiffFQN); - // diff method: diffEntityField(CollectionDiff diff) + // diff method: diffEntityField(CollectionDiff diff) String diffMethodName = "diff" + entityName + fieldSuffix; MethodSource diffMethod = classSource.addMethod() .setName(diffMethodName) .setReturnTypeVoid() .setPublic(); - diffMethod.addParameter("CollectionDiff", "diff"); + diffMethod.addParameter("CollectionDiff", "diff"); diffMethod.setBody(""); String visitMethodName = "visit" + entityName + fieldSuffix + "Item"; @@ -192,13 +194,13 @@ private void createMapFieldMethods(JavaClassSource classSource, String entityNam + ".visitors.diff.CollectionDiff"; classSource.addImport(collectionDiffFQN); - // diff method: diffEntityField(CollectionDiff diff) + // diff method: diffEntityField(CollectionDiff diff) String diffMethodName = "diff" + entityName + fieldSuffix; MethodSource diffMethod = classSource.addMethod() .setName(diffMethodName) .setReturnTypeVoid() .setPublic(); - diffMethod.addParameter("CollectionDiff", "diff"); + diffMethod.addParameter("CollectionDiff", "diff"); diffMethod.setBody(""); String visitMethodName = "visit" + entityName + fieldSuffix; diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index 909e474de..56933fe9b 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -25,13 +25,13 @@ import java.util.List; import java.util.Map; -public class Syn1DiffTraverser extends AbstractDiffTraverser { +public class Syn1DiffTraverser

extends AbstractDiffTraverser> { - public Syn1DiffTraverser(Syn1DiffVisitor visitor) { + public Syn1DiffTraverser(Syn1DiffVisitor

visitor) { super(visitor); } - public Syn1DiffTraverser(Syn1DiffVisitor visitor, PairingStrategyProvider pairingProvider) { + public Syn1DiffTraverser(Syn1DiffVisitor visitor, PairingStrategyProvider

pairingProvider) { super(visitor, pairingProvider); } @@ -70,9 +70,9 @@ public void traverseDocument(Syn1Document original, Syn1Document updated) { traverseNode(original.getInfo(), updated.getInfo()); } { - CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); + CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); visitor.diffDocumentItems(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); @@ -145,42 +145,42 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { visitor.diffSchemaType(original.getType(), updated.getType()); visitor.diffSchemaItems(original.getItems(), updated.getItems()); { - CollectionDiff diff = this.pairMap("properties", original.getProperties(), + CollectionDiff diff = this.pairMap("properties", original.getProperties(), updated.getProperties()); visitor.diffSchemaProperties(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaProperties(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairList("allOf", original.getAllOf(), + CollectionDiff diff = this.pairList("allOf", original.getAllOf(), updated.getAllOf()); visitor.diffSchemaAllOf(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), + CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), updated.getDefinitions()); visitor.diffSchemaDefinitions(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaDefinitions(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), + CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); visitor.diffSchemaNestedSchemas(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaNestedSchemas(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairList("composedSchemas", - original.getComposedSchemas(), updated.getComposedSchemas()); + CollectionDiff diff = this.pairList("composedSchemas", original.getComposedSchemas(), + updated.getComposedSchemas()); visitor.diffSchemaComposedSchemas(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); } } @@ -232,10 +232,10 @@ public void traverseOperation(Syn1Operation original, Syn1Operation updated) { visitor.diffOperationSummary(original.getSummary(), updated.getSummary()); visitor.diffOperationTags(original.getTags(), updated.getTags()); { - CollectionDiff diff = this.pairList("parameters", original.getParameters(), + CollectionDiff diff = this.pairList("parameters", original.getParameters(), updated.getParameters()); visitor.diffOperationParameters(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java index 8411c8ad5..76a1508eb 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -public abstract class Syn1DiffVisitor extends DiffVisitor { +public abstract class Syn1DiffVisitor

extends DiffVisitor { public boolean visitDocument(Syn1Document original, Syn1Document updated) { return true; @@ -35,7 +35,7 @@ public void diffDocumentVersion(String original, String updated) { public void diffDocumentInfo(SynInfo original, SynInfo updated) { } - public void diffDocumentItems(CollectionDiff diff) { + public void diffDocumentItems(CollectionDiff diff) { } public void visitDocumentItemsItem(SynItem original, SynItem updated) { @@ -126,31 +126,31 @@ public void diffSchemaType(String original, String updated) { public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchemaSchemaListUnion updated) { } - public void diffSchemaProperties(CollectionDiff diff) { + public void diffSchemaProperties(CollectionDiff diff) { } public void visitSchemaProperties(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaAllOf(CollectionDiff diff) { + public void diffSchemaAllOf(CollectionDiff diff) { } public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaDefinitions(CollectionDiff diff) { + public void diffSchemaDefinitions(CollectionDiff diff) { } public void visitSchemaDefinitions(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaNestedSchemas(CollectionDiff diff) { + public void diffSchemaNestedSchemas(CollectionDiff diff) { } public void visitSchemaNestedSchemas(SchemaOrBoolean original, SchemaOrBoolean updated) { } - public void diffSchemaComposedSchemas(CollectionDiff diff) { + public void diffSchemaComposedSchemas(CollectionDiff diff) { } public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) { @@ -201,7 +201,7 @@ public void diffOperationSummary(String original, String updated) { public void diffOperationTags(List original, List updated) { } - public void diffOperationParameters(CollectionDiff diff) { + public void diffOperationParameters(CollectionDiff diff) { } public void visitOperationParametersItem(SynItem original, SynItem updated) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index 82e36ca17..95bf3659f 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -25,13 +25,13 @@ import java.util.List; import java.util.Map; -public class Syn2DiffTraverser extends AbstractDiffTraverser { +public class Syn2DiffTraverser

extends AbstractDiffTraverser> { - public Syn2DiffTraverser(Syn2DiffVisitor visitor) { + public Syn2DiffTraverser(Syn2DiffVisitor

visitor) { super(visitor); } - public Syn2DiffTraverser(Syn2DiffVisitor visitor, PairingStrategyProvider pairingProvider) { + public Syn2DiffTraverser(Syn2DiffVisitor visitor, PairingStrategyProvider

pairingProvider) { super(visitor, pairingProvider); } @@ -70,9 +70,9 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { traverseNode(original.getInfo(), updated.getInfo()); } { - CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); + CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); visitor.diffDocumentItems(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); @@ -82,10 +82,10 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { visitor.diffDocumentTags(original.getTags(), updated.getTags()); visitor.diffDocumentMetadata(original.getMetadata(), updated.getMetadata()); { - CollectionDiff diff = this.pairMap("webhooks", original.getWebhooks(), + CollectionDiff diff = this.pairMap("webhooks", original.getWebhooks(), updated.getWebhooks()); visitor.diffDocumentWebhooks(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentWebhooks(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); @@ -158,42 +158,42 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { visitor.diffSchemaType(original.getType(), updated.getType()); visitor.diffSchemaItems(original.getItems(), updated.getItems()); { - CollectionDiff diff = this.pairMap("properties", original.getProperties(), + CollectionDiff diff = this.pairMap("properties", original.getProperties(), updated.getProperties()); visitor.diffSchemaProperties(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaProperties(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairList("allOf", original.getAllOf(), + CollectionDiff diff = this.pairList("allOf", original.getAllOf(), updated.getAllOf()); visitor.diffSchemaAllOf(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), + CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), updated.getDefinitions()); visitor.diffSchemaDefinitions(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaDefinitions(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), + CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); visitor.diffSchemaNestedSchemas(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaNestedSchemas(pair.getOriginal(), pair.getUpdated()); } } { - CollectionDiff diff = this.pairList("composedSchemas", - original.getComposedSchemas(), updated.getComposedSchemas()); + CollectionDiff diff = this.pairList("composedSchemas", original.getComposedSchemas(), + updated.getComposedSchemas()); visitor.diffSchemaComposedSchemas(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); } } @@ -249,10 +249,10 @@ public void traverseOperation(Syn2Operation original, Syn2Operation updated) { visitor.diffOperationSummary(original.getSummary(), updated.getSummary()); visitor.diffOperationTags(original.getTags(), updated.getTags()); { - CollectionDiff diff = this.pairList("parameters", original.getParameters(), + CollectionDiff diff = this.pairList("parameters", original.getParameters(), updated.getParameters()); visitor.diffOperationParameters(diff); - for (CollectionDiff.MatchedPair pair : diff.getMatched()) { + for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java index 9862134ca..54e19e580 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -public abstract class Syn2DiffVisitor extends DiffVisitor { +public abstract class Syn2DiffVisitor

extends DiffVisitor { public boolean visitDocument(Syn2Document original, Syn2Document updated) { return true; @@ -35,7 +35,7 @@ public void diffDocumentVersion(String original, String updated) { public void diffDocumentInfo(SynInfo original, SynInfo updated) { } - public void diffDocumentItems(CollectionDiff diff) { + public void diffDocumentItems(CollectionDiff diff) { } public void visitDocumentItemsItem(SynItem original, SynItem updated) { @@ -47,7 +47,7 @@ public void diffDocumentTags(List original, List updated) { public void diffDocumentMetadata(Map original, Map updated) { } - public void diffDocumentWebhooks(CollectionDiff diff) { + public void diffDocumentWebhooks(CollectionDiff diff) { } public void visitDocumentWebhooks(Syn2PathItem original, Syn2PathItem updated) { @@ -138,31 +138,31 @@ public void diffSchemaType(String original, String updated) { public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchemaSchemaListUnion updated) { } - public void diffSchemaProperties(CollectionDiff diff) { + public void diffSchemaProperties(CollectionDiff diff) { } public void visitSchemaProperties(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaAllOf(CollectionDiff diff) { + public void diffSchemaAllOf(CollectionDiff diff) { } public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaDefinitions(CollectionDiff diff) { + public void diffSchemaDefinitions(CollectionDiff diff) { } public void visitSchemaDefinitions(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaNestedSchemas(CollectionDiff diff) { + public void diffSchemaNestedSchemas(CollectionDiff diff) { } public void visitSchemaNestedSchemas(SchemaOrBoolean original, SchemaOrBoolean updated) { } - public void diffSchemaComposedSchemas(CollectionDiff diff) { + public void diffSchemaComposedSchemas(CollectionDiff diff) { } public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) { @@ -216,7 +216,7 @@ public void diffOperationSummary(String original, String updated) { public void diffOperationTags(List original, List updated) { } - public void diffOperationParameters(CollectionDiff diff) { + public void diffOperationParameters(CollectionDiff diff) { } public void visitOperationParametersItem(SynItem original, SynItem updated) { From fca63a9d8715c6cf9b7959b6ccf646bdb6c84416 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 17:07:32 +0200 Subject: [PATCH 10/16] refactor: pass original/updated collections to diff methods, singularize item visitors (C36) - Collection diff methods now receive (original, updated, diff) instead of just (diff) - Map item visitor methods singularized: visitFullSchemaProperty (not Properties), visitFullSchemaDependency (not Dependencies) - CodeGenContext.singularize made static, reused from both stages --- .../pipe/java/CreateDiffTraversersStage.java | 11 +++++-- .../pipe/java/CreateDiffVisitorsStage.java | 17 ++++++++-- .../umg/pipe/java/method/CodeGenContext.java | 2 +- .../v1/visitors/Syn1DiffTraverser.java | 20 ++++++------ .../v1/visitors/Syn1DiffVisitor.java | 26 ++++++++++------ .../v2/visitors/Syn2DiffTraverser.java | 24 +++++++------- .../v2/visitors/Syn2DiffVisitor.java | 31 ++++++++++++------- 7 files changed, 80 insertions(+), 51 deletions(-) diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index d9ee6fa18..97c89b5c8 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -18,6 +18,7 @@ import io.apitomy.umg.models.java.type.JavaType; import io.apitomy.umg.models.java.type.JavaTypeFactory; import io.apitomy.umg.pipe.java.method.BodyBuilder; +import io.apitomy.umg.pipe.java.method.CodeGenContext; import io.apitomy.umg.pipe.java.method.GetterMethod; /** @@ -203,7 +204,7 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.append("{"); body.append(" CollectionDiff diff = this.pairList(\"${propertyName}\", original.${getter}(), updated.${getter}());"); - body.append(" visitor.${diffMethod}(diff);"); + body.append(" visitor.${diffMethod}(original.${getter}(), updated.${getter}(), diff);"); body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityList(property)) { @@ -219,14 +220,14 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, valueJt.addImportsTo(classSource); String valueTypeStr = valueJt.toJavaTypeString(); - String visitMethod = "visit" + entityName + fieldSuffix; + String visitMethod = "visit" + entityName + singularize(fieldSuffix); body.addContext("valueType", valueTypeStr); body.addContext("visitMethod2", visitMethod); body.addContext("propertyName", property.getName()); body.append("{"); body.append(" CollectionDiff diff = this.pairMap(\"${propertyName}\", original.${getter}(), updated.${getter}());"); - body.append(" visitor.${diffMethod}(diff);"); + body.append(" visitor.${diffMethod}(original.${getter}(), updated.${getter}(), diff);"); body.append(" for (CollectionDiff.MatchedPair pair : diff.getMatched()) {"); body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityMap(property)) { @@ -257,4 +258,8 @@ private String fieldMethodSuffix(PropertyModel property) { } return StringUtils.capitalize(name); } + + private static String singularize(String name) { + return CodeGenContext.singularize(name); + } } diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java index 2aed39ba8..dff102f3c 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java @@ -8,6 +8,7 @@ import org.jboss.forge.roaster.model.source.MethodSource; import io.apitomy.umg.beans.SpecificationVersion; +import io.apitomy.umg.pipe.java.method.CodeGenContext; import io.apitomy.umg.models.concept.EntityModel; import io.apitomy.umg.models.concept.PropertyModel; import io.apitomy.umg.models.concept.NamespaceModel; @@ -165,12 +166,15 @@ private void createListFieldMethods(JavaClassSource classSource, String entityNa + ".visitors.diff.CollectionDiff"; classSource.addImport(collectionDiffFQN); - // diff method: diffEntityField(CollectionDiff diff) + classSource.addImport(java.util.List.class); + String diffMethodName = "diff" + entityName + fieldSuffix; MethodSource diffMethod = classSource.addMethod() .setName(diffMethodName) .setReturnTypeVoid() .setPublic(); + diffMethod.addParameter("List<" + valueJt.toJavaTypeString() + ">", "original"); + diffMethod.addParameter("List<" + valueJt.toJavaTypeString() + ">", "updated"); diffMethod.addParameter("CollectionDiff", "diff"); diffMethod.setBody(""); @@ -193,17 +197,20 @@ private void createMapFieldMethods(JavaClassSource classSource, String entityNam String collectionDiffFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.CollectionDiff"; classSource.addImport(collectionDiffFQN); + classSource.addImport(java.util.Map.class); - // diff method: diffEntityField(CollectionDiff diff) String diffMethodName = "diff" + entityName + fieldSuffix; MethodSource diffMethod = classSource.addMethod() .setName(diffMethodName) .setReturnTypeVoid() .setPublic(); + diffMethod.addParameter("Map", "original"); + diffMethod.addParameter("Map", "updated"); diffMethod.addParameter("CollectionDiff", "diff"); diffMethod.setBody(""); - String visitMethodName = "visit" + entityName + fieldSuffix; + String singularSuffix = singularize(fieldSuffix); + String visitMethodName = "visit" + entityName + singularSuffix; MethodSource visitMethod = classSource.addMethod() .setName(visitMethodName) .setReturnTypeVoid() @@ -213,6 +220,10 @@ private void createMapFieldMethods(JavaClassSource classSource, String entityNam visitMethod.setBody(""); } + private static String singularize(String name) { + return CodeGenContext.singularize(name); + } + /** * Turns a property name into a method name suffix. * "title" → "Title", "$ref" → "$ref", "allOf" → "AllOf" diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/method/CodeGenContext.java b/generator/src/main/java/io/apitomy/umg/pipe/java/method/CodeGenContext.java index 9caa1b62b..b176c8621 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/method/CodeGenContext.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/method/CodeGenContext.java @@ -262,7 +262,7 @@ public Class primitiveTypeToClass(Type type) { return PrimitiveTypeUtil.primitiveTypeToClass(type); } - public String singularize(String name) { + public static String singularize(String name) { return inflector.singularize(name); } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index 56933fe9b..2c28f5f00 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -71,7 +71,7 @@ public void traverseDocument(Syn1Document original, Syn1Document updated) { } { CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); - visitor.diffDocumentItems(diff); + visitor.diffDocumentItems(original.getItems(), updated.getItems(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { @@ -147,15 +147,15 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { { CollectionDiff diff = this.pairMap("properties", original.getProperties(), updated.getProperties()); - visitor.diffSchemaProperties(diff); + visitor.diffSchemaProperties(original.getProperties(), updated.getProperties(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaProperties(pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaProperty(pair.getOriginal(), pair.getUpdated()); } } { CollectionDiff diff = this.pairList("allOf", original.getAllOf(), updated.getAllOf()); - visitor.diffSchemaAllOf(diff); + visitor.diffSchemaAllOf(original.getAllOf(), updated.getAllOf(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); } @@ -163,23 +163,23 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { { CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), updated.getDefinitions()); - visitor.diffSchemaDefinitions(diff); + visitor.diffSchemaDefinitions(original.getDefinitions(), updated.getDefinitions(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaDefinitions(pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaDefinition(pair.getOriginal(), pair.getUpdated()); } } { CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); - visitor.diffSchemaNestedSchemas(diff); + visitor.diffSchemaNestedSchemas(original.getNestedSchemas(), updated.getNestedSchemas(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaNestedSchemas(pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaNestedSchema(pair.getOriginal(), pair.getUpdated()); } } { CollectionDiff diff = this.pairList("composedSchemas", original.getComposedSchemas(), updated.getComposedSchemas()); - visitor.diffSchemaComposedSchemas(diff); + visitor.diffSchemaComposedSchemas(original.getComposedSchemas(), updated.getComposedSchemas(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); } @@ -234,7 +234,7 @@ public void traverseOperation(Syn1Operation original, Syn1Operation updated) { { CollectionDiff diff = this.pairList("parameters", original.getParameters(), updated.getParameters()); - visitor.diffOperationParameters(diff); + visitor.diffOperationParameters(original.getParameters(), updated.getParameters(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java index 76a1508eb..f401138ea 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java @@ -35,7 +35,7 @@ public void diffDocumentVersion(String original, String updated) { public void diffDocumentInfo(SynInfo original, SynInfo updated) { } - public void diffDocumentItems(CollectionDiff diff) { + public void diffDocumentItems(List original, List updated, CollectionDiff diff) { } public void visitDocumentItemsItem(SynItem original, SynItem updated) { @@ -126,31 +126,36 @@ public void diffSchemaType(String original, String updated) { public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchemaSchemaListUnion updated) { } - public void diffSchemaProperties(CollectionDiff diff) { + public void diffSchemaProperties(Map original, Map updated, + CollectionDiff diff) { } - public void visitSchemaProperties(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaProperty(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaAllOf(CollectionDiff diff) { + public void diffSchemaAllOf(List original, List updated, + CollectionDiff diff) { } public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaDefinitions(CollectionDiff diff) { + public void diffSchemaDefinitions(Map original, Map updated, + CollectionDiff diff) { } - public void visitSchemaDefinitions(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaDefinition(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaNestedSchemas(CollectionDiff diff) { + public void diffSchemaNestedSchemas(Map original, Map updated, + CollectionDiff diff) { } - public void visitSchemaNestedSchemas(SchemaOrBoolean original, SchemaOrBoolean updated) { + public void visitSchemaNestedSchema(SchemaOrBoolean original, SchemaOrBoolean updated) { } - public void diffSchemaComposedSchemas(CollectionDiff diff) { + public void diffSchemaComposedSchemas(List original, List updated, + CollectionDiff diff) { } public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) { @@ -201,7 +206,8 @@ public void diffOperationSummary(String original, String updated) { public void diffOperationTags(List original, List updated) { } - public void diffOperationParameters(CollectionDiff diff) { + public void diffOperationParameters(List original, List updated, + CollectionDiff diff) { } public void visitOperationParametersItem(SynItem original, SynItem updated) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index 95bf3659f..99586c36a 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -71,7 +71,7 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { } { CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); - visitor.diffDocumentItems(diff); + visitor.diffDocumentItems(original.getItems(), updated.getItems(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { @@ -84,9 +84,9 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { { CollectionDiff diff = this.pairMap("webhooks", original.getWebhooks(), updated.getWebhooks()); - visitor.diffDocumentWebhooks(diff); + visitor.diffDocumentWebhooks(original.getWebhooks(), updated.getWebhooks(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitDocumentWebhooks(pair.getOriginal(), pair.getUpdated()); + visitor.visitDocumentWebhook(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { traverseNode(pair.getOriginal(), pair.getUpdated()); } @@ -160,15 +160,15 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { { CollectionDiff diff = this.pairMap("properties", original.getProperties(), updated.getProperties()); - visitor.diffSchemaProperties(diff); + visitor.diffSchemaProperties(original.getProperties(), updated.getProperties(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaProperties(pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaProperty(pair.getOriginal(), pair.getUpdated()); } } { CollectionDiff diff = this.pairList("allOf", original.getAllOf(), updated.getAllOf()); - visitor.diffSchemaAllOf(diff); + visitor.diffSchemaAllOf(original.getAllOf(), updated.getAllOf(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); } @@ -176,23 +176,23 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { { CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(), updated.getDefinitions()); - visitor.diffSchemaDefinitions(diff); + visitor.diffSchemaDefinitions(original.getDefinitions(), updated.getDefinitions(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaDefinitions(pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaDefinition(pair.getOriginal(), pair.getUpdated()); } } { CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(), updated.getNestedSchemas()); - visitor.diffSchemaNestedSchemas(diff); + visitor.diffSchemaNestedSchemas(original.getNestedSchemas(), updated.getNestedSchemas(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { - visitor.visitSchemaNestedSchemas(pair.getOriginal(), pair.getUpdated()); + visitor.visitSchemaNestedSchema(pair.getOriginal(), pair.getUpdated()); } } { CollectionDiff diff = this.pairList("composedSchemas", original.getComposedSchemas(), updated.getComposedSchemas()); - visitor.diffSchemaComposedSchemas(diff); + visitor.diffSchemaComposedSchemas(original.getComposedSchemas(), updated.getComposedSchemas(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); } @@ -251,7 +251,7 @@ public void traverseOperation(Syn2Operation original, Syn2Operation updated) { { CollectionDiff diff = this.pairList("parameters", original.getParameters(), updated.getParameters()); - visitor.diffOperationParameters(diff); + visitor.diffOperationParameters(original.getParameters(), updated.getParameters(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java index 54e19e580..9eb257b0f 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java @@ -35,7 +35,7 @@ public void diffDocumentVersion(String original, String updated) { public void diffDocumentInfo(SynInfo original, SynInfo updated) { } - public void diffDocumentItems(CollectionDiff diff) { + public void diffDocumentItems(List original, List updated, CollectionDiff diff) { } public void visitDocumentItemsItem(SynItem original, SynItem updated) { @@ -47,10 +47,11 @@ public void diffDocumentTags(List original, List updated) { public void diffDocumentMetadata(Map original, Map updated) { } - public void diffDocumentWebhooks(CollectionDiff diff) { + public void diffDocumentWebhooks(Map original, Map updated, + CollectionDiff diff) { } - public void visitDocumentWebhooks(Syn2PathItem original, Syn2PathItem updated) { + public void visitDocumentWebhook(Syn2PathItem original, Syn2PathItem updated) { } public void diffDocumentAdditionalSchema(SchemaOrBoolean original, SchemaOrBoolean updated) { @@ -138,31 +139,36 @@ public void diffSchemaType(String original, String updated) { public void diffSchemaItems(BooleanSchemaSchemaListUnion original, BooleanSchemaSchemaListUnion updated) { } - public void diffSchemaProperties(CollectionDiff diff) { + public void diffSchemaProperties(Map original, Map updated, + CollectionDiff diff) { } - public void visitSchemaProperties(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaProperty(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaAllOf(CollectionDiff diff) { + public void diffSchemaAllOf(List original, List updated, + CollectionDiff diff) { } public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaDefinitions(CollectionDiff diff) { + public void diffSchemaDefinitions(Map original, Map updated, + CollectionDiff diff) { } - public void visitSchemaDefinitions(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + public void visitSchemaDefinition(BooleanSchemaUnion original, BooleanSchemaUnion updated) { } - public void diffSchemaNestedSchemas(CollectionDiff diff) { + public void diffSchemaNestedSchemas(Map original, Map updated, + CollectionDiff diff) { } - public void visitSchemaNestedSchemas(SchemaOrBoolean original, SchemaOrBoolean updated) { + public void visitSchemaNestedSchema(SchemaOrBoolean original, SchemaOrBoolean updated) { } - public void diffSchemaComposedSchemas(CollectionDiff diff) { + public void diffSchemaComposedSchemas(List original, List updated, + CollectionDiff diff) { } public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) { @@ -216,7 +222,8 @@ public void diffOperationSummary(String original, String updated) { public void diffOperationTags(List original, List updated) { } - public void diffOperationParameters(CollectionDiff diff) { + public void diffOperationParameters(List original, List updated, + CollectionDiff diff) { } public void visitOperationParametersItem(SynItem original, SynItem updated) { From c571a9d22f8930870aa48b4c08474e2d4f3948a6 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 30 Jun 2026 18:09:26 +0200 Subject: [PATCH 11/16] fix: exclude visitors/diff from JSweet transpilation (C36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The diff infrastructure uses generics (P type parameter) that cause JSweet to hang during transpilation. The diff traverser/visitor are Java-only — not needed in TypeScript. --- data-models/pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data-models/pom.xml b/data-models/pom.xml index 233ea3a0a..d96d51684 100644 --- a/data-models/pom.xml +++ b/data-models/pom.xml @@ -191,6 +191,9 @@ **/_*/** **/jsonschema/compat/** **/jsonschema/ref/** + **/visitors/diff/** + **/*DiffTraverser.java + **/*DiffVisitor.java From 57eb14b3e491fe70291d0238aff73b93da5d61e9 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Wed, 1 Jul 2026 01:47:25 +0200 Subject: [PATCH 12/16] refactor: remove DiffVisitor base, rename strategies, add traverse(Any) (C36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove empty DiffVisitor base class — generated visitors are standalone - Rename IndexPairingStrategy → DefaultListPairingStrategy - Rename KeyPairingStrategy → DefaultMapPairingStrategy - Add traverse(Any, Any) public entry point on AbstractDiffTraverser (matches AbstractTraverser pattern, supports union roots like boolean schemas) - Remove V extends DiffVisitor bound — just V - Fix unchecked cast in default constructor --- .../umg/pipe/java/CreateDiffVisitorsStage.java | 5 ----- .../umg/pipe/java/LoadBaseClassesStage.java | 5 ++--- .../visitors/diff/AbstractDiffTraverser.java | 17 +++++++++++++++-- ...gy.java => DefaultListPairingStrategy.java} | 2 +- ...egy.java => DefaultMapPairingStrategy.java} | 2 +- .../diff/DefaultPairingStrategyProvider.java | 4 ++-- .../umg/base/visitors/diff/DiffVisitor.java | 8 -------- .../io/apitomy/umg/full/expected-manifest.txt | 5 ++--- .../synthetic/v1/visitors/Syn1DiffVisitor.java | 3 +-- .../synthetic/v2/visitors/Syn2DiffVisitor.java | 3 +-- .../visitors/diff/AbstractDiffTraverser.java | 18 ++++++++++++++++-- ...gy.java => DefaultListPairingStrategy.java} | 2 +- ...egy.java => DefaultMapPairingStrategy.java} | 2 +- .../diff/DefaultPairingStrategyProvider.java | 4 ++-- .../synthetic/visitors/diff/DiffVisitor.java | 8 -------- 15 files changed, 45 insertions(+), 43 deletions(-) rename generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/{IndexPairingStrategy.java => DefaultListPairingStrategy.java} (88%) rename generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/{KeyPairingStrategy.java => DefaultMapPairingStrategy.java} (89%) delete mode 100644 generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java rename generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/{IndexPairingStrategy.java => DefaultListPairingStrategy.java} (87%) rename generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/{KeyPairingStrategy.java => DefaultMapPairingStrategy.java} (88%) delete mode 100644 generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java index dff102f3c..85c633305 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java @@ -43,11 +43,6 @@ private void createDiffVisitor(SpecificationVersion specVer) { classSource.addTypeVariable("P"); - String baseFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.DiffVisitor"; - JavaClassSource baseSource = getState().getJavaIndex().lookupClass(baseFQN); - classSource.addImport(baseSource); - classSource.extendSuperType(baseSource); - JavaTypeFactory jtf = getJavaTypeFactory(); specVer.getEntities().forEach(entity -> { diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java index b0792420e..4ae524014 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java @@ -41,10 +41,9 @@ protected void doProcess() { "io.apitomy.umg.base.visitors.diff.AbstractDiffTraverser", "io.apitomy.umg.base.visitors.diff.CollectionDiff", "io.apitomy.umg.base.visitors.diff.DefaultPairingKey", + "io.apitomy.umg.base.visitors.diff.DefaultListPairingStrategy", + "io.apitomy.umg.base.visitors.diff.DefaultMapPairingStrategy", "io.apitomy.umg.base.visitors.diff.DefaultPairingStrategyProvider", - "io.apitomy.umg.base.visitors.diff.DiffVisitor", - "io.apitomy.umg.base.visitors.diff.IndexPairingStrategy", - "io.apitomy.umg.base.visitors.diff.KeyPairingStrategy", "io.apitomy.umg.base.union.ListUnionValueImpl", "io.apitomy.umg.base.union.MapUnionValueImpl", "io.apitomy.umg.base.union.EntityListUnionValueImpl", diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java index 7c7ca87fc..d68e5c451 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -3,23 +3,27 @@ import java.util.List; import java.util.Map; +import io.apitomy.umg.base.Any; import io.apitomy.umg.base.Node; +import io.apitomy.umg.base.union.Union; /** * Base class for generated diff traversers. Provides shared collection pairing logic. * Subclasses (generated per spec version) provide entity-specific field iteration * and call typed visitor methods. * + * @param

the pairing key type * @param the spec-version-specific DiffVisitor subclass */ -public class AbstractDiffTraverser { +public class AbstractDiffTraverser { protected final V visitor; protected final PairingStrategyProvider

pairingProvider; @SuppressWarnings("unchecked") protected AbstractDiffTraverser(V visitor) { - this(visitor, (PairingStrategyProvider

) (PairingStrategyProvider) new DefaultPairingStrategyProvider()); + this.visitor = visitor; + this.pairingProvider = (PairingStrategyProvider

) new DefaultPairingStrategyProvider(); } protected AbstractDiffTraverser(V visitor, PairingStrategyProvider

pairingProvider) { @@ -27,6 +31,15 @@ protected AbstractDiffTraverser(V visitor, PairingStrategyProvider

pairingPro this.pairingProvider = pairingProvider; } + /** + * Public entry point — accepts Any (Node or Union). + */ + public void traverse(Any original, Any updated) { + if (original instanceof Node || updated instanceof Node) { + traverseNode((Node) original, (Node) updated); + } + } + protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { MapPairingStrategy strategy = pairingProvider.getMapStrategy(propertyName); return strategy.pair(original, updated); diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultListPairingStrategy.java similarity index 88% rename from generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java rename to generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultListPairingStrategy.java index 00cf7714b..9fbbbf744 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultListPairingStrategy.java @@ -7,7 +7,7 @@ /** * Default list pairing strategy — pairs entries by index. */ -public class IndexPairingStrategy implements ListPairingStrategy { +public class DefaultListPairingStrategy implements ListPairingStrategy { @Override public CollectionDiff pair(List original, List updated) { diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultMapPairingStrategy.java similarity index 89% rename from generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java rename to generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultMapPairingStrategy.java index 1a313bbb8..de2a7ecdd 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultMapPairingStrategy.java @@ -7,7 +7,7 @@ /** * Default map pairing strategy — pairs entries by key. */ -public class KeyPairingStrategy implements MapPairingStrategy { +public class DefaultMapPairingStrategy implements MapPairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java index 757a60ca9..9340ccb28 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultPairingStrategyProvider.java @@ -7,11 +7,11 @@ public class DefaultPairingStrategyProvider implements PairingStrategyProvider MapPairingStrategy getMapStrategy(String propertyName) { - return new KeyPairingStrategy<>(); + return new DefaultMapPairingStrategy<>(); } @Override public ListPairingStrategy getListStrategy(String propertyName) { - return new IndexPairingStrategy<>(); + return new DefaultListPairingStrategy<>(); } } diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java deleted file mode 100644 index ab5c011cd..000000000 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DiffVisitor.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.apitomy.umg.base.visitors.diff; - -/** - * Base class for generated per-spec-version DiffVisitors. Generated subclasses - * add typed, field-specific methods for each entity and property. - */ -public abstract class DiffVisitor { -} diff --git a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt index 2d7617781..4cf209eb7 100644 --- a/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt +++ b/generator/src/test/resources/io/apitomy/umg/full/expected-manifest.txt @@ -1430,11 +1430,10 @@ io/apitomy/datamodels/models/visitors/TraversingVisitor.java io/apitomy/datamodels/models/visitors/Visitor.java io/apitomy/datamodels/models/visitors/diff/AbstractDiffTraverser.java io/apitomy/datamodels/models/visitors/diff/CollectionDiff.java +io/apitomy/datamodels/models/visitors/diff/DefaultListPairingStrategy.java +io/apitomy/datamodels/models/visitors/diff/DefaultMapPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/DefaultPairingKey.java io/apitomy/datamodels/models/visitors/diff/DefaultPairingStrategyProvider.java -io/apitomy/datamodels/models/visitors/diff/DiffVisitor.java -io/apitomy/datamodels/models/visitors/diff/IndexPairingStrategy.java -io/apitomy/datamodels/models/visitors/diff/KeyPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/ListPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/MapPairingStrategy.java io/apitomy/datamodels/models/visitors/diff/PairingStrategyProvider.java diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java index f401138ea..5a79f48c6 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java @@ -19,11 +19,10 @@ import io.test.synthetic.v1.Syn1Paths; import io.test.synthetic.v1.Syn1Schema; import io.test.synthetic.visitors.diff.CollectionDiff; -import io.test.synthetic.visitors.diff.DiffVisitor; import java.util.List; import java.util.Map; -public abstract class Syn1DiffVisitor

extends DiffVisitor { +public abstract class Syn1DiffVisitor

{ public boolean visitDocument(Syn1Document original, Syn1Document updated) { return true; diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java index 9eb257b0f..bbbdb38f0 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java @@ -19,11 +19,10 @@ import io.test.synthetic.v2.Syn2Paths; import io.test.synthetic.v2.Syn2Schema; import io.test.synthetic.visitors.diff.CollectionDiff; -import io.test.synthetic.visitors.diff.DiffVisitor; import java.util.List; import java.util.Map; -public abstract class Syn2DiffVisitor

extends DiffVisitor { +public abstract class Syn2DiffVisitor

{ public boolean visitDocument(Syn2Document original, Syn2Document updated) { return true; diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java index e1feb1ed6..5b12d7db9 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java @@ -1,6 +1,8 @@ package io.test.synthetic.visitors.diff; +import io.test.synthetic.Any; import io.test.synthetic.Node; +import io.test.synthetic.union.Union; import java.util.List; import java.util.Map; @@ -9,17 +11,20 @@ * logic. Subclasses (generated per spec version) provide entity-specific field * iteration and call typed visitor methods. * + * @param

+ * the pairing key type * @param * the spec-version-specific DiffVisitor subclass */ -public class AbstractDiffTraverser { +public class AbstractDiffTraverser { protected final V visitor; protected final PairingStrategyProvider

pairingProvider; @SuppressWarnings("unchecked") protected AbstractDiffTraverser(V visitor) { - this(visitor, (PairingStrategyProvider

) (PairingStrategyProvider) new DefaultPairingStrategyProvider()); + this.visitor = visitor; + this.pairingProvider = (PairingStrategyProvider

) new DefaultPairingStrategyProvider(); } protected AbstractDiffTraverser(V visitor, PairingStrategyProvider

pairingProvider) { @@ -27,6 +32,15 @@ protected AbstractDiffTraverser(V visitor, PairingStrategyProvider

pairingPro this.pairingProvider = pairingProvider; } + /** + * Public entry point — accepts Any (Node or Union). + */ + public void traverse(Any original, Any updated) { + if (original instanceof Node || updated instanceof Node) { + traverseNode((Node) original, (Node) updated); + } + } + protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { MapPairingStrategy strategy = pairingProvider.getMapStrategy(propertyName); return strategy.pair(original, updated); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultListPairingStrategy.java similarity index 87% rename from generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java rename to generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultListPairingStrategy.java index d4c0f7c8c..07c079a08 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/IndexPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultListPairingStrategy.java @@ -7,7 +7,7 @@ /** * Default list pairing strategy — pairs entries by index. */ -public class IndexPairingStrategy implements ListPairingStrategy { +public class DefaultListPairingStrategy implements ListPairingStrategy { @Override public CollectionDiff pair(List original, List updated) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultMapPairingStrategy.java similarity index 88% rename from generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java rename to generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultMapPairingStrategy.java index 589fb606d..74c710be0 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/KeyPairingStrategy.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultMapPairingStrategy.java @@ -7,7 +7,7 @@ /** * Default map pairing strategy — pairs entries by key. */ -public class KeyPairingStrategy implements MapPairingStrategy { +public class DefaultMapPairingStrategy implements MapPairingStrategy { @Override public CollectionDiff pair(Map original, Map updated) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java index be8276368..a2e04f5aa 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultPairingStrategyProvider.java @@ -7,11 +7,11 @@ public class DefaultPairingStrategyProvider implements PairingStrategyProvider MapPairingStrategy getMapStrategy(String propertyName) { - return new KeyPairingStrategy<>(); + return new DefaultMapPairingStrategy<>(); } @Override public ListPairingStrategy getListStrategy(String propertyName) { - return new IndexPairingStrategy<>(); + return new DefaultListPairingStrategy<>(); } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java deleted file mode 100644 index 25c55f040..000000000 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DiffVisitor.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.test.synthetic.visitors.diff; - -/** - * Base class for generated per-spec-version DiffVisitors. Generated subclasses - * add typed, field-specific methods for each entity and property. - */ -public abstract class DiffVisitor { -} From 3adf4858e2533ff985c6015909403f1ca7069e95 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Wed, 1 Jul 2026 02:07:03 +0200 Subject: [PATCH 13/16] feat(generator): union traverse methods, auto-recursion for entity fields (C36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generated DiffVisitor now has diff methods for each union type: - diffJsonSchema(JsonSchema, JsonSchema) - diffStringStringListUnion(StringStringListUnion, StringStringListUnion) Generated DiffTraverser now has traverse methods for each union type: - traverseJsonSchema calls visitor.diffJsonSchema, then auto-recurses into traverseFullSchema if both sides are the same entity variant - Union fields (not, if/then/else, additionalProperties, etc.) now call traverseUnionType for auto-recursion instead of just notifying visitor Handles true → {} (boolean schema to object schema): visitor.diffJsonSchema gets called with mixed variants, no recursion occurs. --- .../pipe/java/CreateDiffTraversersStage.java | 60 +++++++++++++++++++ .../pipe/java/CreateDiffVisitorsStage.java | 26 ++++++++ .../v1/visitors/Syn1DiffTraverser.java | 31 ++++++++++ .../v1/visitors/Syn1DiffVisitor.java | 10 ++++ .../v2/visitors/Syn2DiffTraverser.java | 31 ++++++++++ .../v2/visitors/Syn2DiffVisitor.java | 10 ++++ 6 files changed, 168 insertions(+) diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index 97c89b5c8..1e1f6cec5 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -96,6 +96,20 @@ private void createDiffTraverser(SpecificationVersion specVer) { } }); + // Create traverse methods for union types that contain entities + String namespace = specVer.getNamespace(); + var nsModel = getState().getConceptIndex().lookupNamespace(namespace); + getState().getConceptIndex().findTypes(namespace).stream() + .filter(t -> t instanceof io.apitomy.umg.models.concept.type.UnionType) + .map(t -> (io.apitomy.umg.models.concept.type.UnionType) t) + .forEach(unionType -> { + var jt = getJavaTypeFactory().createJavaType(unionType, nsModel); + String unionMethodName = "traverse" + jt.getSimpleName(); + if (createdMethods.add(unionMethodName)) { + createUnionTraverseMethod(classSource, unionType, jt, namespace); + } + }); + getState().getJavaIndex().index(classSource); } @@ -240,7 +254,10 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, } else if (isUnion(property)) { JavaType jt = jtf.createJavaType(property.getResolvedType(), ns); jt.addImportsTo(classSource); + String traverseUnion = "traverse" + jt.getSimpleName(); + body.addContext("traverseUnion", traverseUnion); body.append("visitor.${diffMethod}(original.${getter}(), updated.${getter}());"); + body.append("this.${traverseUnion}(original.${getter}(), updated.${getter}());"); } else if (isPrimitive(property) || isPrimitiveList(property) || isPrimitiveMap(property)) { JavaType jt = jtf.createJavaType(property.getResolvedType(), ns); jt.addImportsTo(classSource); @@ -259,6 +276,49 @@ private String fieldMethodSuffix(PropertyModel property) { return StringUtils.capitalize(name); } + private void createUnionTraverseMethod(JavaClassSource classSource, + io.apitomy.umg.models.concept.type.UnionType unionType, + JavaType unionJt, String namespace) { + String unionTypeName = unionJt.getSimpleName(); + String methodName = "traverse" + unionTypeName; + String diffMethodName = "diff" + unionTypeName; + + unionJt.addImportsTo(classSource); + + MethodSource method = classSource.addMethod() + .setName(methodName) + .setReturnTypeVoid() + .setPublic(); + method.addParameter(unionJt.toJavaTypeString(), "original"); + method.addParameter(unionJt.toJavaTypeString(), "updated"); + + BodyBuilder body = new BodyBuilder(); + body.append("if (original == null && updated == null) return;"); + body.addContext("diffMethod", diffMethodName); + body.append("visitor.${diffMethod}(original, updated);"); + + // Auto-recurse into entity variants when both sides are the same entity type + for (var variantType : unionType.getTypes()) { + if (variantType.isEntityType()) { + var entity = ((io.apitomy.umg.models.concept.type.EntityType) variantType).getEntity(); + if (entity == null) { + entity = getState().getConceptIndex().lookupEntity(namespace, variantType.getName()); + } + if (entity != null) { + var entityInterface = lookupJavaEntity(entity); + classSource.addImport(entityInterface); + body.addContext("entityType", entityInterface.getName()); + body.addContext("traverseEntity", "traverse" + entity.getName()); + body.append("if (original instanceof ${entityType} && updated instanceof ${entityType}) {"); + body.append(" this.${traverseEntity}((${entityType}) original, (${entityType}) updated);"); + body.append("}"); + } + } + } + + method.setBody(body.toString()); + } + private static String singularize(String name) { return CodeGenContext.singularize(name); } diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java index 85c633305..99f375f5a 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java @@ -56,6 +56,28 @@ private void createDiffVisitor(SpecificationVersion specVer) { createPropertyMethods(classSource, entityModel, jtf); }); + // Create diff methods for union types + String namespace = specVer.getNamespace(); + var nsModel = getState().getConceptIndex().lookupNamespace(namespace); + getState().getConceptIndex().findTypes(namespace).stream() + .filter(t -> t instanceof io.apitomy.umg.models.concept.type.UnionType) + .map(t -> (io.apitomy.umg.models.concept.type.UnionType) t) + .forEach(unionType -> { + var jt = jtf.createJavaType(unionType, nsModel); + jt.addImportsTo(classSource); + String unionTypeName = jt.getSimpleName(); + String methodName = "diff" + unionTypeName; + if (!hasMethod(classSource, methodName)) { + MethodSource method = classSource.addMethod() + .setName(methodName) + .setReturnTypeVoid() + .setPublic(); + method.addParameter(jt.toJavaTypeString(), "original"); + method.addParameter(jt.toJavaTypeString(), "updated"); + method.setBody(""); + } + }); + getState().getJavaIndex().index(classSource); } @@ -230,4 +252,8 @@ private String fieldMethodSuffix(PropertyModel property) { } return StringUtils.capitalize(name); } + + private boolean hasMethod(JavaClassSource classSource, String methodName) { + return classSource.getMethods().stream().anyMatch(m -> m.getName().equals(methodName)); + } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index 2c28f5f00..62a51a2f1 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -82,6 +82,7 @@ public void traverseDocument(Syn1Document original, Syn1Document updated) { visitor.diffDocumentTags(original.getTags(), updated.getTags()); visitor.diffDocumentMetadata(original.getMetadata(), updated.getMetadata()); visitor.diffDocumentAdditionalSchema(original.getAdditionalSchema(), updated.getAdditionalSchema()); + this.traverseSchemaOrBoolean(original.getAdditionalSchema(), updated.getAdditionalSchema()); } public void traverseInfo(Syn1Info original, Syn1Info updated) { @@ -131,6 +132,7 @@ public void traverseItem(Syn1Item original, Syn1Item updated) { } visitor.diffItemExamples(original.getExamples(), updated.getExamples()); visitor.diffItemDefaultValue(original.getDefaultValue(), updated.getDefaultValue()); + this.traverseBooleanSchemaUnion(original.getDefaultValue(), updated.getDefaultValue()); visitor.diffItemTitle(original.getTitle(), updated.getTitle()); } @@ -144,6 +146,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { visitor.diffSchema$ref(original.get$ref(), updated.get$ref()); visitor.diffSchemaType(original.getType(), updated.getType()); visitor.diffSchemaItems(original.getItems(), updated.getItems()); + this.traverseBooleanSchemaSchemaListUnion(original.getItems(), updated.getItems()); { CollectionDiff diff = this.pairMap("properties", original.getProperties(), updated.getProperties()); @@ -243,4 +246,32 @@ public void traverseOperation(Syn1Operation original, Syn1Operation updated) { } } } + + public void traverseSchemaOrBoolean(SchemaOrBoolean original, SchemaOrBoolean updated) { + if (original == null && updated == null) + return; + visitor.diffSchemaOrBoolean(original, updated); + if (original instanceof Syn1Schema && updated instanceof Syn1Schema) { + this.traverseSchema((Syn1Schema) original, (Syn1Schema) updated); + } + } + + public void traverseBooleanSchemaSchemaListUnion(BooleanSchemaSchemaListUnion original, + BooleanSchemaSchemaListUnion updated) { + if (original == null && updated == null) + return; + visitor.diffBooleanSchemaSchemaListUnion(original, updated); + if (original instanceof Syn1Schema && updated instanceof Syn1Schema) { + this.traverseSchema((Syn1Schema) original, (Syn1Schema) updated); + } + } + + public void traverseBooleanSchemaUnion(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + if (original == null && updated == null) + return; + visitor.diffBooleanSchemaUnion(original, updated); + if (original instanceof Syn1Schema && updated instanceof Syn1Schema) { + this.traverseSchema((Syn1Schema) original, (Syn1Schema) updated); + } + } } \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java index 5a79f48c6..64b8dcd99 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java @@ -211,4 +211,14 @@ public void diffOperationParameters(List original, List update public void visitOperationParametersItem(SynItem original, SynItem updated) { } + + public void diffSchemaOrBoolean(SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public void diffBooleanSchemaSchemaListUnion(BooleanSchemaSchemaListUnion original, + BooleanSchemaSchemaListUnion updated) { + } + + public void diffBooleanSchemaUnion(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } } \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index 99586c36a..84b701f2e 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -93,6 +93,7 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { } } visitor.diffDocumentAdditionalSchema(original.getAdditionalSchema(), updated.getAdditionalSchema()); + this.traverseSchemaOrBoolean(original.getAdditionalSchema(), updated.getAdditionalSchema()); } public void traverseInfo(Syn2Info original, Syn2Info updated) { @@ -143,6 +144,7 @@ public void traverseItem(Syn2Item original, Syn2Item updated) { } visitor.diffItemExamples(original.getExamples(), updated.getExamples()); visitor.diffItemDefaultValue(original.getDefaultValue(), updated.getDefaultValue()); + this.traverseBooleanSchemaUnion(original.getDefaultValue(), updated.getDefaultValue()); visitor.diffItemTitle(original.getTitle(), updated.getTitle()); visitor.diffItemDeprecated(original.isDeprecated(), updated.isDeprecated()); } @@ -157,6 +159,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { visitor.diffSchema$ref(original.get$ref(), updated.get$ref()); visitor.diffSchemaType(original.getType(), updated.getType()); visitor.diffSchemaItems(original.getItems(), updated.getItems()); + this.traverseBooleanSchemaSchemaListUnion(original.getItems(), updated.getItems()); { CollectionDiff diff = this.pairMap("properties", original.getProperties(), updated.getProperties()); @@ -260,4 +263,32 @@ public void traverseOperation(Syn2Operation original, Syn2Operation updated) { } } } + + public void traverseSchemaOrBoolean(SchemaOrBoolean original, SchemaOrBoolean updated) { + if (original == null && updated == null) + return; + visitor.diffSchemaOrBoolean(original, updated); + if (original instanceof Syn2Schema && updated instanceof Syn2Schema) { + this.traverseSchema((Syn2Schema) original, (Syn2Schema) updated); + } + } + + public void traverseBooleanSchemaSchemaListUnion(BooleanSchemaSchemaListUnion original, + BooleanSchemaSchemaListUnion updated) { + if (original == null && updated == null) + return; + visitor.diffBooleanSchemaSchemaListUnion(original, updated); + if (original instanceof Syn2Schema && updated instanceof Syn2Schema) { + this.traverseSchema((Syn2Schema) original, (Syn2Schema) updated); + } + } + + public void traverseBooleanSchemaUnion(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + if (original == null && updated == null) + return; + visitor.diffBooleanSchemaUnion(original, updated); + if (original instanceof Syn2Schema && updated instanceof Syn2Schema) { + this.traverseSchema((Syn2Schema) original, (Syn2Schema) updated); + } + } } \ No newline at end of file diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java index bbbdb38f0..c2387eef7 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java @@ -227,4 +227,14 @@ public void diffOperationParameters(List original, List update public void visitOperationParametersItem(SynItem original, SynItem updated) { } + + public void diffSchemaOrBoolean(SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public void diffBooleanSchemaSchemaListUnion(BooleanSchemaSchemaListUnion original, + BooleanSchemaSchemaListUnion updated) { + } + + public void diffBooleanSchemaUnion(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } } \ No newline at end of file From 969bde3304663aaf7f8b17272b95c8a98bccd602 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Wed, 1 Jul 2026 02:12:16 +0200 Subject: [PATCH 14/16] fix(generator): add auto-recursion for union collections in DiffTraverser (C36) Union list/map matched pairs now call traverseUnionType instead of being silently skipped. E.g., dependencies map pairs call traverseDependency, allOf list pairs call traverseJsonSchema. Entity collections still use traverseNode as before. --- .../umg/pipe/java/CreateDiffTraversersStage.java | 12 ++++++++++++ .../synthetic/v1/visitors/Syn1DiffTraverser.java | 5 +++++ .../synthetic/v2/visitors/Syn2DiffTraverser.java | 5 +++++ 3 files changed, 22 insertions(+) diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index 1e1f6cec5..988e43d16 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -225,6 +225,12 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); body.append(" traverseNode(pair.getOriginal(), pair.getUpdated());"); body.append(" }"); + } else if (isUnionList(property)) { + ListType lt = (ListType) property.getResolvedType(); + JavaType unionJt = jtf.createJavaType(lt.getValueType(), ns); + String traverseUnion = "traverse" + unionJt.getSimpleName(); + body.addContext("traverseUnion", traverseUnion); + body.append(" this.${traverseUnion}(pair.getOriginal(), pair.getUpdated());"); } body.append(" }"); body.append("}"); @@ -248,6 +254,12 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); body.append(" traverseNode(pair.getOriginal(), pair.getUpdated());"); body.append(" }"); + } else if (isUnionMap(property)) { + MapType mt = (MapType) property.getResolvedType(); + JavaType unionJt = jtf.createJavaType(mt.getValueType(), ns); + String traverseUnion = "traverse" + unionJt.getSimpleName(); + body.addContext("traverseUnion", traverseUnion); + body.append(" this.${traverseUnion}(pair.getOriginal(), pair.getUpdated());"); } body.append(" }"); body.append("}"); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index 62a51a2f1..ad8a0f68b 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -153,6 +153,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { visitor.diffSchemaProperties(original.getProperties(), updated.getProperties(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaProperty(pair.getOriginal(), pair.getUpdated()); + this.traverseBooleanSchemaUnion(pair.getOriginal(), pair.getUpdated()); } } { @@ -161,6 +162,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { visitor.diffSchemaAllOf(original.getAllOf(), updated.getAllOf(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); + this.traverseBooleanSchemaUnion(pair.getOriginal(), pair.getUpdated()); } } { @@ -169,6 +171,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { visitor.diffSchemaDefinitions(original.getDefinitions(), updated.getDefinitions(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaDefinition(pair.getOriginal(), pair.getUpdated()); + this.traverseBooleanSchemaUnion(pair.getOriginal(), pair.getUpdated()); } } { @@ -177,6 +180,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { visitor.diffSchemaNestedSchemas(original.getNestedSchemas(), updated.getNestedSchemas(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaNestedSchema(pair.getOriginal(), pair.getUpdated()); + this.traverseSchemaOrBoolean(pair.getOriginal(), pair.getUpdated()); } } { @@ -185,6 +189,7 @@ public void traverseSchema(Syn1Schema original, Syn1Schema updated) { visitor.diffSchemaComposedSchemas(original.getComposedSchemas(), updated.getComposedSchemas(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); + this.traverseSchemaOrBoolean(pair.getOriginal(), pair.getUpdated()); } } visitor.diffSchemaMinLength(original.getMinLength(), updated.getMinLength()); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index 84b701f2e..63ffaf534 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -166,6 +166,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { visitor.diffSchemaProperties(original.getProperties(), updated.getProperties(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaProperty(pair.getOriginal(), pair.getUpdated()); + this.traverseBooleanSchemaUnion(pair.getOriginal(), pair.getUpdated()); } } { @@ -174,6 +175,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { visitor.diffSchemaAllOf(original.getAllOf(), updated.getAllOf(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaAllOfItem(pair.getOriginal(), pair.getUpdated()); + this.traverseBooleanSchemaUnion(pair.getOriginal(), pair.getUpdated()); } } { @@ -182,6 +184,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { visitor.diffSchemaDefinitions(original.getDefinitions(), updated.getDefinitions(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaDefinition(pair.getOriginal(), pair.getUpdated()); + this.traverseBooleanSchemaUnion(pair.getOriginal(), pair.getUpdated()); } } { @@ -190,6 +193,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { visitor.diffSchemaNestedSchemas(original.getNestedSchemas(), updated.getNestedSchemas(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaNestedSchema(pair.getOriginal(), pair.getUpdated()); + this.traverseSchemaOrBoolean(pair.getOriginal(), pair.getUpdated()); } } { @@ -198,6 +202,7 @@ public void traverseSchema(Syn2Schema original, Syn2Schema updated) { visitor.diffSchemaComposedSchemas(original.getComposedSchemas(), updated.getComposedSchemas(), diff); for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitSchemaComposedSchemasItem(pair.getOriginal(), pair.getUpdated()); + this.traverseSchemaOrBoolean(pair.getOriginal(), pair.getUpdated()); } } visitor.diffSchemaMinLength(original.getMinLength(), updated.getMinLength()); From a7a621c7f9fadd3b5f8d513386538138c577d230 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Wed, 1 Jul 2026 02:21:24 +0200 Subject: [PATCH 15/16] refactor: replace traverseNode with traverse(Any, Any) dispatch (C36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generated traverse(Any, Any) now dispatches to both union and entity traverse methods. Union types are checked first (instanceof) since entity implementations also implement their union interfaces. Removed traverseNode from AbstractDiffTraverser — all dispatch is now in the generated traverse override. Entity field recursion and collection matched pair recursion call traverse() instead of traverseNode(). --- .../pipe/java/CreateDiffTraversersStage.java | 55 +++++++++++++++---- .../visitors/diff/AbstractDiffTraverser.java | 13 +---- .../v1/visitors/Syn1DiffTraverser.java | 32 +++++++---- .../v2/visitors/Syn2DiffTraverser.java | 36 +++++++----- .../visitors/diff/AbstractDiffTraverser.java | 15 +---- 5 files changed, 92 insertions(+), 59 deletions(-) diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index 988e43d16..0f9107fd8 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -83,7 +83,7 @@ private void createDiffTraverser(SpecificationVersion specVer) { java.util.Set createdMethods = new java.util.HashSet<>(); - createTraverseNodeDispatch(specVer, classSource, createdMethods); + createTraverseDispatch(specVer, classSource, createdMethods); specVer.getEntities().forEach(entity -> { EntityModel entityModel = getState().getConceptIndex() @@ -113,24 +113,54 @@ private void createDiffTraverser(SpecificationVersion specVer) { getState().getJavaIndex().index(classSource); } - private void createTraverseNodeDispatch(SpecificationVersion specVer, JavaClassSource classSource, + private void createTraverseDispatch(SpecificationVersion specVer, JavaClassSource classSource, java.util.Set createdMethods) { - createdMethods.add("traverseNode"); + createdMethods.add("traverse"); + + String anyFQN = getState().getConfig().getRootNamespace() + ".Any"; + classSource.addImport(anyFQN); + MethodSource method = classSource.addMethod() - .setName("traverseNode") + .setName("traverse") .setReturnTypeVoid() - .setProtected(); - method.addParameter("Node", "original"); - method.addParameter("Node", "updated"); + .setPublic(); + method.addParameter("Any", "original"); + method.addParameter("Any", "updated"); method.addAnnotation(Override.class); BodyBuilder body = new BodyBuilder(); - body.append("Node target = original != null ? original : updated;"); + body.append("Any target = original != null ? original : updated;"); + body.append("if (target == null) return;"); boolean first = true; + String namespace = specVer.getNamespace(); + var nsModel = getState().getConceptIndex().lookupNamespace(namespace); + + // Union types first — they encompass entity types + for (var type : getState().getConceptIndex().findTypes(namespace)) { + if (!(type instanceof io.apitomy.umg.models.concept.type.UnionType)) continue; + var unionType = (io.apitomy.umg.models.concept.type.UnionType) type; + var jt = getJavaTypeFactory().createJavaType(unionType, nsModel); + jt.addImportsTo(classSource); + String unionTypeName = jt.getSimpleName(); + + body.addContext("unionType", unionTypeName); + String methodName = "traverse" + unionTypeName; + body.addContext("methodName", methodName); + + if (first) { + body.append("if (target instanceof ${unionType}) {"); + first = false; + } else { + body.append("} else if (target instanceof ${unionType}) {"); + } + body.append(" this.${methodName}((${unionType}) original, (${unionType}) updated);"); + } + + // Entity types not covered by unions for (var entity : specVer.getEntities()) { EntityModel entityModel = getState().getConceptIndex() - .lookupEntity(specVer.getNamespace() + "." + entity.getName()); + .lookupEntity(namespace + "." + entity.getName()); if (entityModel == null) continue; JavaInterfaceSource javaEntity = lookupJavaEntity(entityModel); @@ -149,6 +179,7 @@ private void createTraverseNodeDispatch(SpecificationVersion specVer, JavaClassS } body.append(" this.${methodName}((${entityType}) original, (${entityType}) updated);"); } + if (!first) { body.append("}"); } @@ -203,7 +234,7 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, jt.addImportsTo(classSource); body.append("visitor.${diffMethod}(original.${getter}(), updated.${getter}());"); body.append("if (original.${getter}() != null && updated.${getter}() != null) {"); - body.append(" traverseNode(original.${getter}(), updated.${getter}());"); + body.append(" traverse(original.${getter}(), updated.${getter}());"); body.append("}"); } else if (isEntityList(property) || isUnionList(property)) { ListType listType = (ListType) property.getResolvedType(); @@ -223,7 +254,7 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityList(property)) { body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); - body.append(" traverseNode(pair.getOriginal(), pair.getUpdated());"); + body.append(" traverse(pair.getOriginal(), pair.getUpdated());"); body.append(" }"); } else if (isUnionList(property)) { ListType lt = (ListType) property.getResolvedType(); @@ -252,7 +283,7 @@ private void createEntityTraverseMethod(SpecificationVersion specVer, body.append(" visitor.${visitMethod2}(pair.getOriginal(), pair.getUpdated());"); if (isEntityMap(property)) { body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {"); - body.append(" traverseNode(pair.getOriginal(), pair.getUpdated());"); + body.append(" traverse(pair.getOriginal(), pair.getUpdated());"); body.append(" }"); } else if (isUnionMap(property)) { MapType mt = (MapType) property.getResolvedType(); diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java index d68e5c451..425817bed 100644 --- a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -4,8 +4,6 @@ import java.util.Map; import io.apitomy.umg.base.Any; -import io.apitomy.umg.base.Node; -import io.apitomy.umg.base.union.Union; /** * Base class for generated diff traversers. Provides shared collection pairing logic. @@ -33,11 +31,10 @@ protected AbstractDiffTraverser(V visitor, PairingStrategyProvider

pairingPro /** * Public entry point — accepts Any (Node or Union). + * Generated subclasses override this with type-based dispatch + * to union traverse methods and entity traverse methods. */ public void traverse(Any original, Any updated) { - if (original instanceof Node || updated instanceof Node) { - traverseNode((Node) original, (Node) updated); - } } protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { @@ -50,10 +47,4 @@ protected CollectionDiff pairList(String propertyName, List origina return strategy.pair(original, updated); } - /** - * Dispatches to the correct entity-specific traverse method. - * Generated subclasses override this with type-based dispatch. - */ - protected void traverseNode(Node original, Node updated) { - } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index ad8a0f68b..2dff7ca1c 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.test.synthetic.Any; import io.test.synthetic.BooleanSchemaSchemaListUnion; import io.test.synthetic.BooleanSchemaUnion; import io.test.synthetic.Node; @@ -36,9 +37,18 @@ public Syn1DiffTraverser(Syn1DiffVisitor visitor, PairingStrategyProvider

pai } @Override - protected void traverseNode(Node original, Node updated) { - Node target = original != null ? original : updated; - if (target instanceof Syn1Document) { + public void traverse(Any original, Any updated) { + Any target = original != null ? original : updated; + if (target == null) + return; + if (target instanceof SchemaOrBoolean) { + this.traverseSchemaOrBoolean((SchemaOrBoolean) original, (SchemaOrBoolean) updated); + } else if (target instanceof BooleanSchemaSchemaListUnion) { + this.traverseBooleanSchemaSchemaListUnion((BooleanSchemaSchemaListUnion) original, + (BooleanSchemaSchemaListUnion) updated); + } else if (target instanceof BooleanSchemaUnion) { + this.traverseBooleanSchemaUnion((BooleanSchemaUnion) original, (BooleanSchemaUnion) updated); + } else if (target instanceof Syn1Document) { this.traverseDocument((Syn1Document) original, (Syn1Document) updated); } else if (target instanceof Syn1Info) { this.traverseInfo((Syn1Info) original, (Syn1Info) updated); @@ -67,7 +77,7 @@ public void traverseDocument(Syn1Document original, Syn1Document updated) { visitor.diffDocumentVersion(original.getVersion(), updated.getVersion()); visitor.diffDocumentInfo(original.getInfo(), updated.getInfo()); if (original.getInfo() != null && updated.getInfo() != null) { - traverseNode(original.getInfo(), updated.getInfo()); + traverse(original.getInfo(), updated.getInfo()); } { CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); @@ -75,7 +85,7 @@ public void traverseDocument(Syn1Document original, Syn1Document updated) { for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { - traverseNode(pair.getOriginal(), pair.getUpdated()); + traverse(pair.getOriginal(), pair.getUpdated()); } } } @@ -95,7 +105,7 @@ public void traverseInfo(Syn1Info original, Syn1Info updated) { visitor.diffInfoName(original.getName(), updated.getName()); visitor.diffInfoContact(original.getContact(), updated.getContact()); if (original.getContact() != null && updated.getContact() != null) { - traverseNode(original.getContact(), updated.getContact()); + traverse(original.getContact(), updated.getContact()); } visitor.diffInfoVersion(original.getVersion(), updated.getVersion()); } @@ -128,7 +138,7 @@ public void traverseItem(Syn1Item original, Syn1Item updated) { visitor.diffItemRaw(original.getRaw(), updated.getRaw()); visitor.diffItemSchema(original.getSchema(), updated.getSchema()); if (original.getSchema() != null && updated.getSchema() != null) { - traverseNode(original.getSchema(), updated.getSchema()); + traverse(original.getSchema(), updated.getSchema()); } visitor.diffItemExamples(original.getExamples(), updated.getExamples()); visitor.diffItemDefaultValue(original.getDefaultValue(), updated.getDefaultValue()); @@ -217,15 +227,15 @@ public void traversePathItem(Syn1PathItem original, Syn1PathItem updated) { visitor.diffPathItemSummary(original.getSummary(), updated.getSummary()); visitor.diffPathItemGet(original.getGet(), updated.getGet()); if (original.getGet() != null && updated.getGet() != null) { - traverseNode(original.getGet(), updated.getGet()); + traverse(original.getGet(), updated.getGet()); } visitor.diffPathItemPut(original.getPut(), updated.getPut()); if (original.getPut() != null && updated.getPut() != null) { - traverseNode(original.getPut(), updated.getPut()); + traverse(original.getPut(), updated.getPut()); } visitor.diffPathItemPost(original.getPost(), updated.getPost()); if (original.getPost() != null && updated.getPost() != null) { - traverseNode(original.getPost(), updated.getPost()); + traverse(original.getPost(), updated.getPost()); } } @@ -246,7 +256,7 @@ public void traverseOperation(Syn1Operation original, Syn1Operation updated) { for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { - traverseNode(pair.getOriginal(), pair.getUpdated()); + traverse(pair.getOriginal(), pair.getUpdated()); } } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index 63ffaf534..8d560f840 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.test.synthetic.Any; import io.test.synthetic.BooleanSchemaSchemaListUnion; import io.test.synthetic.BooleanSchemaUnion; import io.test.synthetic.Node; @@ -36,9 +37,18 @@ public Syn2DiffTraverser(Syn2DiffVisitor visitor, PairingStrategyProvider

pai } @Override - protected void traverseNode(Node original, Node updated) { - Node target = original != null ? original : updated; - if (target instanceof Syn2Document) { + public void traverse(Any original, Any updated) { + Any target = original != null ? original : updated; + if (target == null) + return; + if (target instanceof SchemaOrBoolean) { + this.traverseSchemaOrBoolean((SchemaOrBoolean) original, (SchemaOrBoolean) updated); + } else if (target instanceof BooleanSchemaSchemaListUnion) { + this.traverseBooleanSchemaSchemaListUnion((BooleanSchemaSchemaListUnion) original, + (BooleanSchemaSchemaListUnion) updated); + } else if (target instanceof BooleanSchemaUnion) { + this.traverseBooleanSchemaUnion((BooleanSchemaUnion) original, (BooleanSchemaUnion) updated); + } else if (target instanceof Syn2Document) { this.traverseDocument((Syn2Document) original, (Syn2Document) updated); } else if (target instanceof Syn2Info) { this.traverseInfo((Syn2Info) original, (Syn2Info) updated); @@ -67,7 +77,7 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { visitor.diffDocumentVersion(original.getVersion(), updated.getVersion()); visitor.diffDocumentInfo(original.getInfo(), updated.getInfo()); if (original.getInfo() != null && updated.getInfo() != null) { - traverseNode(original.getInfo(), updated.getInfo()); + traverse(original.getInfo(), updated.getInfo()); } { CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems()); @@ -75,7 +85,7 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentItemsItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { - traverseNode(pair.getOriginal(), pair.getUpdated()); + traverse(pair.getOriginal(), pair.getUpdated()); } } } @@ -88,7 +98,7 @@ public void traverseDocument(Syn2Document original, Syn2Document updated) { for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitDocumentWebhook(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { - traverseNode(pair.getOriginal(), pair.getUpdated()); + traverse(pair.getOriginal(), pair.getUpdated()); } } } @@ -106,7 +116,7 @@ public void traverseInfo(Syn2Info original, Syn2Info updated) { visitor.diffInfoName(original.getName(), updated.getName()); visitor.diffInfoContact(original.getContact(), updated.getContact()); if (original.getContact() != null && updated.getContact() != null) { - traverseNode(original.getContact(), updated.getContact()); + traverse(original.getContact(), updated.getContact()); } visitor.diffInfoVersion(original.getVersion(), updated.getVersion()); visitor.diffInfoLicense(original.getLicense(), updated.getLicense()); @@ -140,7 +150,7 @@ public void traverseItem(Syn2Item original, Syn2Item updated) { visitor.diffItemRaw(original.getRaw(), updated.getRaw()); visitor.diffItemSchema(original.getSchema(), updated.getSchema()); if (original.getSchema() != null && updated.getSchema() != null) { - traverseNode(original.getSchema(), updated.getSchema()); + traverse(original.getSchema(), updated.getSchema()); } visitor.diffItemExamples(original.getExamples(), updated.getExamples()); visitor.diffItemDefaultValue(original.getDefaultValue(), updated.getDefaultValue()); @@ -230,19 +240,19 @@ public void traversePathItem(Syn2PathItem original, Syn2PathItem updated) { visitor.diffPathItemSummary(original.getSummary(), updated.getSummary()); visitor.diffPathItemGet(original.getGet(), updated.getGet()); if (original.getGet() != null && updated.getGet() != null) { - traverseNode(original.getGet(), updated.getGet()); + traverse(original.getGet(), updated.getGet()); } visitor.diffPathItemPut(original.getPut(), updated.getPut()); if (original.getPut() != null && updated.getPut() != null) { - traverseNode(original.getPut(), updated.getPut()); + traverse(original.getPut(), updated.getPut()); } visitor.diffPathItemPost(original.getPost(), updated.getPost()); if (original.getPost() != null && updated.getPost() != null) { - traverseNode(original.getPost(), updated.getPost()); + traverse(original.getPost(), updated.getPost()); } visitor.diffPathItemDelete(original.getDelete(), updated.getDelete()); if (original.getDelete() != null && updated.getDelete() != null) { - traverseNode(original.getDelete(), updated.getDelete()); + traverse(original.getDelete(), updated.getDelete()); } } @@ -263,7 +273,7 @@ public void traverseOperation(Syn2Operation original, Syn2Operation updated) { for (CollectionDiff.MatchedPair pair : diff.getMatched()) { visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated()); if (pair.getOriginal() != null && pair.getUpdated() != null) { - traverseNode(pair.getOriginal(), pair.getUpdated()); + traverse(pair.getOriginal(), pair.getUpdated()); } } } diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java index 5b12d7db9..abe40943e 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java @@ -1,8 +1,6 @@ package io.test.synthetic.visitors.diff; import io.test.synthetic.Any; -import io.test.synthetic.Node; -import io.test.synthetic.union.Union; import java.util.List; import java.util.Map; @@ -33,12 +31,11 @@ protected AbstractDiffTraverser(V visitor, PairingStrategyProvider

pairingPro } /** - * Public entry point — accepts Any (Node or Union). + * Public entry point — accepts Any (Node or Union). Generated subclasses + * override this with type-based dispatch to union traverse methods and entity + * traverse methods. */ public void traverse(Any original, Any updated) { - if (original instanceof Node || updated instanceof Node) { - traverseNode((Node) original, (Node) updated); - } } protected CollectionDiff pairMap(String propertyName, Map original, Map updated) { @@ -51,10 +48,4 @@ protected CollectionDiff pairList(String propertyName, List origina return strategy.pair(original, updated); } - /** - * Dispatches to the correct entity-specific traverse method. Generated - * subclasses override this with type-based dispatch. - */ - protected void traverseNode(Node original, Node updated) { - } } From caf138f131da5a81e205e32140e8f2c4d0298bc9 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Wed, 1 Jul 2026 02:23:47 +0200 Subject: [PATCH 16/16] fix: skip entity dispatch branches covered by union dispatch (C36) Entities that are variants of a union type are already matched by the union instanceof check. Don't emit redundant entity branches that would be dead code (e.g., JD7FullSchema is covered by JsonSchema). --- .../umg/pipe/java/CreateDiffTraversersStage.java | 13 +++++++++++++ .../synthetic/v1/visitors/Syn1DiffTraverser.java | 2 -- .../synthetic/v2/visitors/Syn2DiffTraverser.java | 2 -- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java index 0f9107fd8..dcb7b87c3 100644 --- a/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -157,11 +157,24 @@ private void createTraverseDispatch(SpecificationVersion specVer, JavaClassSourc body.append(" this.${methodName}((${unionType}) original, (${unionType}) updated);"); } + // Collect entity names that are variants of unions (already dispatched above) + java.util.Set unionEntityNames = new java.util.HashSet<>(); + for (var type : getState().getConceptIndex().findTypes(namespace)) { + if (type instanceof io.apitomy.umg.models.concept.type.UnionType) { + for (var variant : ((io.apitomy.umg.models.concept.type.UnionType) type).getTypes()) { + if (variant.isEntityType()) { + unionEntityNames.add(variant.getName()); + } + } + } + } + // Entity types not covered by unions for (var entity : specVer.getEntities()) { EntityModel entityModel = getState().getConceptIndex() .lookupEntity(namespace + "." + entity.getName()); if (entityModel == null) continue; + if (unionEntityNames.contains(entityModel.getName())) continue; JavaInterfaceSource javaEntity = lookupJavaEntity(entityModel); classSource.addImport(javaEntity); diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java index 2dff7ca1c..3ef819599 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java @@ -56,8 +56,6 @@ public void traverse(Any original, Any updated) { this.traverseContact((Syn1Contact) original, (Syn1Contact) updated); } else if (target instanceof Syn1Item) { this.traverseItem((Syn1Item) original, (Syn1Item) updated); - } else if (target instanceof Syn1Schema) { - this.traverseSchema((Syn1Schema) original, (Syn1Schema) updated); } else if (target instanceof Syn1Paths) { this.traversePaths((Syn1Paths) original, (Syn1Paths) updated); } else if (target instanceof Syn1PathItem) { diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java index 8d560f840..c90460199 100644 --- a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java @@ -56,8 +56,6 @@ public void traverse(Any original, Any updated) { this.traverseContact((Syn2Contact) original, (Syn2Contact) updated); } else if (target instanceof Syn2Item) { this.traverseItem((Syn2Item) original, (Syn2Item) updated); - } else if (target instanceof Syn2Schema) { - this.traverseSchema((Syn2Schema) original, (Syn2Schema) updated); } else if (target instanceof Syn2Paths) { this.traversePaths((Syn2Paths) original, (Syn2Paths) updated); } else if (target instanceof Syn2PathItem) {