>");
+
+ // 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);
+
+ // Import PairingStrategyProvider
+ String providerFQN = getState().getConfig().getRootNamespace() + ".visitors.diff.PairingStrategyProvider";
+ classSource.addImport(providerFQN);
+
+ // Constructors
+ MethodSource ", "visitor");
+ constructor.setBody("super(visitor);");
+
+ MethodSource ", "pairingProvider");
+ constructor2.setBody("super(visitor, pairingProvider);");
+
+ java.util.Set diff = this.pairList(\"${propertyName}\", original.${getter}(), updated.${getter}());");
+ 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)) {
+ body.append(" if (pair.getOriginal() != null && pair.getUpdated() != null) {");
+ body.append(" traverse(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("}");
+ } else if (isEntityMap(property) || isUnionMap(property)) {
+ MapType mapType = (MapType) property.getResolvedType();
+ JavaType valueJt = jtf.createJavaType(mapType.getValueType(), ns);
+ valueJt.addImportsTo(classSource);
+
+ String valueTypeStr = valueJt.toJavaTypeString();
+ 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}(original.${getter}(), updated.${getter}(), diff);");
+ 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) {");
+ body.append(" traverse(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("}");
+ } 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);
+ 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);
+ }
+
+ 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 ", "diff");
+ diffMethod.setBody("");
+
+ String visitMethodName = "visit" + entityName + fieldSuffix + "Item";
+ MethodSource ", "diff");
+ diffMethod.setBody("");
+
+ String singularSuffix = singularize(fieldSuffix);
+ String visitMethodName = "visit" + entityName + singularSuffix;
+ MethodSource the pairing key type
+ * @param {
+
+ protected final V visitor;
+ protected final PairingStrategyProvider pairingProvider;
+
+ @SuppressWarnings("unchecked")
+ protected AbstractDiffTraverser(V visitor) {
+ this.visitor = visitor;
+ this.pairingProvider = (PairingStrategyProvider ) new DefaultPairingStrategyProvider();
+ }
+
+ protected AbstractDiffTraverser(V visitor, PairingStrategyProvider pairingProvider) {
+ this.visitor = visitor;
+ this.pairingProvider = pairingProvider;
+ }
+
+ /**
+ * 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) {
+ }
+
+ protected pairMap(String propertyName, Map strategy = pairingProvider.getMapStrategy(propertyName);
+ return strategy.pair(original, updated);
+ }
+
+ protected pairList(String propertyName, List strategy = pairingProvider.getListStrategy(propertyName);
+ 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
new file mode 100644
index 000000000..3f84383fe
--- /dev/null
+++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/CollectionDiff.java
@@ -0,0 +1,105 @@
+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,
+ * or matched between the original and updated collections.
+ *
+ * @param the pairing key type (e.g., Integer for index-based pairing)
+ * @param {
+
+ CollectionDiff pair(List the pairing key type (e.g., String for key-based pairing)
+ * @param {
+
+ CollectionDiff pair(Map {
+
+ getMapStrategy(String propertyName);
+
+ 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 35946a1d6..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
@@ -178,6 +178,8 @@ 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/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
@@ -250,6 +252,8 @@ 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/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
@@ -324,6 +328,8 @@ 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/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
@@ -398,6 +404,8 @@ 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/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
@@ -472,6 +480,8 @@ 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/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
@@ -546,6 +556,8 @@ 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/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
@@ -620,6 +632,8 @@ 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/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
@@ -736,6 +750,8 @@ 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/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
@@ -815,6 +831,8 @@ 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/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
@@ -846,6 +864,8 @@ 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/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
@@ -858,6 +878,8 @@ 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/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
@@ -870,6 +892,8 @@ 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/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
@@ -885,6 +909,8 @@ 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/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
@@ -897,6 +923,8 @@ 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/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
@@ -1034,6 +1062,8 @@ 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/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
@@ -1144,6 +1174,8 @@ 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/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
@@ -1218,6 +1250,8 @@ 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/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
@@ -1304,6 +1338,8 @@ 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/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
@@ -1348,6 +1384,8 @@ 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/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
@@ -1390,3 +1428,12 @@ 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/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/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
new file mode 100644
index 000000000..3ef819599
--- /dev/null
+++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffTraverser.java
@@ -0,0 +1,290 @@
+package io.test.synthetic.v1.visitors;
+
+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;
+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.AbstractDiffTraverser;
+import io.test.synthetic.visitors.diff.CollectionDiff;
+import io.test.synthetic.visitors.diff.PairingStrategyProvider;
+import java.util.List;
+import java.util.Map;
+
+public class Syn1DiffTraverser extends AbstractDiffTraverser > {
+
+ public Syn1DiffTraverser(Syn1DiffVisitor visitor) {
+ super(visitor);
+ }
+
+ public Syn1DiffTraverser(Syn1DiffVisitor visitor, PairingStrategyProvider pairingProvider) {
+ super(visitor, pairingProvider);
+ }
+
+ @Override
+ 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);
+ } 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 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.visitDocument(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ visitor.diffDocumentVersion(original.getVersion(), updated.getVersion());
+ visitor.diffDocumentInfo(original.getInfo(), updated.getInfo());
+ if (original.getInfo() != null && updated.getInfo() != null) {
+ traverse(original.getInfo(), updated.getInfo());
+ }
+ {
+ CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems());
+ 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) {
+ traverse(pair.getOriginal(), pair.getUpdated());
+ }
+ }
+ }
+ 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) {
+ if (original == null && updated == null)
+ return;
+ if (!visitor.visitInfo(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ visitor.diffInfoName(original.getName(), updated.getName());
+ visitor.diffInfoContact(original.getContact(), updated.getContact());
+ if (original.getContact() != null && updated.getContact() != null) {
+ traverse(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.visitContact(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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.visitItem(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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) {
+ traverse(original.getSchema(), updated.getSchema());
+ }
+ visitor.diffItemExamples(original.getExamples(), updated.getExamples());
+ visitor.diffItemDefaultValue(original.getDefaultValue(), updated.getDefaultValue());
+ this.traverseBooleanSchemaUnion(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.visitSchema(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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());
+ 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());
+ }
+ }
+ {
+ CollectionDiff diff = this.pairList("allOf", original.getAllOf(),
+ updated.getAllOf());
+ 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());
+ }
+ }
+ {
+ CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(),
+ updated.getDefinitions());
+ 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());
+ }
+ }
+ {
+ CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(),
+ updated.getNestedSchemas());
+ 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());
+ }
+ }
+ {
+ CollectionDiff diff = this.pairList("composedSchemas", original.getComposedSchemas(),
+ updated.getComposedSchemas());
+ 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());
+ 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.visitPaths(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ }
+
+ public void traversePathItem(Syn1PathItem original, Syn1PathItem updated) {
+ if (original == null && updated == null)
+ return;
+ if (!visitor.visitPathItem(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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) {
+ traverse(original.getGet(), updated.getGet());
+ }
+ visitor.diffPathItemPut(original.getPut(), updated.getPut());
+ if (original.getPut() != null && updated.getPut() != null) {
+ traverse(original.getPut(), updated.getPut());
+ }
+ visitor.diffPathItemPost(original.getPost(), updated.getPost());
+ if (original.getPost() != null && updated.getPost() != null) {
+ traverse(original.getPost(), updated.getPost());
+ }
+ }
+
+ public void traverseOperation(Syn1Operation original, Syn1Operation updated) {
+ if (original == null && updated == null)
+ return;
+ if (!visitor.visitOperation(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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(original.getParameters(), updated.getParameters(), diff);
+ for (CollectionDiff.MatchedPair pair : diff.getMatched()) {
+ visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated());
+ if (pair.getOriginal() != null && pair.getUpdated() != null) {
+ traverse(pair.getOriginal(), pair.getUpdated());
+ }
+ }
+ }
+ }
+
+ 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
new file mode 100644
index 000000000..64b8dcd99
--- /dev/null
+++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v1/visitors/Syn1DiffVisitor.java
@@ -0,0 +1,224 @@
+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 java.util.List;
+import java.util.Map;
+
+public abstract class Syn1DiffVisitor {
+
+ 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(List diff) {
+ }
+
+ public void visitDocumentItemsItem(SynItem original, SynItem updated) {
+ }
+
+ public void diffDocumentTags(List diff) {
+ }
+
+ public void visitSchemaProperty(BooleanSchemaUnion original, BooleanSchemaUnion updated) {
+ }
+
+ public void diffSchemaAllOf(List diff) {
+ }
+
+ public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) {
+ }
+
+ public void diffSchemaDefinitions(Map diff) {
+ }
+
+ public void visitSchemaDefinition(BooleanSchemaUnion original, BooleanSchemaUnion updated) {
+ }
+
+ public void diffSchemaNestedSchemas(Map diff) {
+ }
+
+ public void visitSchemaNestedSchema(SchemaOrBoolean original, SchemaOrBoolean updated) {
+ }
+
+ public void diffSchemaComposedSchemas(List diff) {
+ }
+
+ public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) {
+ }
+
+ public void diffSchemaMinLength(Integer original, Integer updated) {
+ }
+
+ public void diffSchemaMaxLength(Integer original, Integer updated) {
+ }
+
+ public void diffSchemaEnum(List diff) {
+ }
+
+ 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
new file mode 100644
index 000000000..c90460199
--- /dev/null
+++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffTraverser.java
@@ -0,0 +1,307 @@
+package io.test.synthetic.v2.visitors;
+
+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;
+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.AbstractDiffTraverser;
+import io.test.synthetic.visitors.diff.CollectionDiff;
+import io.test.synthetic.visitors.diff.PairingStrategyProvider;
+import java.util.List;
+import java.util.Map;
+
+public class Syn2DiffTraverser extends AbstractDiffTraverser > {
+
+ public Syn2DiffTraverser(Syn2DiffVisitor visitor) {
+ super(visitor);
+ }
+
+ public Syn2DiffTraverser(Syn2DiffVisitor visitor, PairingStrategyProvider pairingProvider) {
+ super(visitor, pairingProvider);
+ }
+
+ @Override
+ 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);
+ } 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 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.visitDocument(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ visitor.diffDocumentVersion(original.getVersion(), updated.getVersion());
+ visitor.diffDocumentInfo(original.getInfo(), updated.getInfo());
+ if (original.getInfo() != null && updated.getInfo() != null) {
+ traverse(original.getInfo(), updated.getInfo());
+ }
+ {
+ CollectionDiff diff = this.pairList("items", original.getItems(), updated.getItems());
+ 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) {
+ traverse(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(original.getWebhooks(), updated.getWebhooks(), diff);
+ for (CollectionDiff.MatchedPair pair : diff.getMatched()) {
+ visitor.visitDocumentWebhook(pair.getOriginal(), pair.getUpdated());
+ if (pair.getOriginal() != null && pair.getUpdated() != null) {
+ traverse(pair.getOriginal(), pair.getUpdated());
+ }
+ }
+ }
+ visitor.diffDocumentAdditionalSchema(original.getAdditionalSchema(), updated.getAdditionalSchema());
+ this.traverseSchemaOrBoolean(original.getAdditionalSchema(), updated.getAdditionalSchema());
+ }
+
+ public void traverseInfo(Syn2Info original, Syn2Info updated) {
+ if (original == null && updated == null)
+ return;
+ if (!visitor.visitInfo(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ visitor.diffInfoName(original.getName(), updated.getName());
+ visitor.diffInfoContact(original.getContact(), updated.getContact());
+ if (original.getContact() != null && updated.getContact() != null) {
+ traverse(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.visitContact(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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.visitItem(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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) {
+ traverse(original.getSchema(), updated.getSchema());
+ }
+ 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());
+ }
+
+ public void traverseSchema(Syn2Schema original, Syn2Schema updated) {
+ if (original == null && updated == null)
+ return;
+ if (!visitor.visitSchema(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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());
+ 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());
+ }
+ }
+ {
+ CollectionDiff diff = this.pairList("allOf", original.getAllOf(),
+ updated.getAllOf());
+ 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());
+ }
+ }
+ {
+ CollectionDiff diff = this.pairMap("definitions", original.getDefinitions(),
+ updated.getDefinitions());
+ 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());
+ }
+ }
+ {
+ CollectionDiff diff = this.pairMap("nestedSchemas", original.getNestedSchemas(),
+ updated.getNestedSchemas());
+ 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());
+ }
+ }
+ {
+ CollectionDiff diff = this.pairList("composedSchemas", original.getComposedSchemas(),
+ updated.getComposedSchemas());
+ 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());
+ 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.visitPaths(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ }
+
+ public void traversePathItem(Syn2PathItem original, Syn2PathItem updated) {
+ if (original == null && updated == null)
+ return;
+ if (!visitor.visitPathItem(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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) {
+ traverse(original.getGet(), updated.getGet());
+ }
+ visitor.diffPathItemPut(original.getPut(), updated.getPut());
+ if (original.getPut() != null && updated.getPut() != null) {
+ traverse(original.getPut(), updated.getPut());
+ }
+ visitor.diffPathItemPost(original.getPost(), updated.getPost());
+ if (original.getPost() != null && updated.getPost() != null) {
+ traverse(original.getPost(), updated.getPost());
+ }
+ visitor.diffPathItemDelete(original.getDelete(), updated.getDelete());
+ if (original.getDelete() != null && updated.getDelete() != null) {
+ traverse(original.getDelete(), updated.getDelete());
+ }
+ }
+
+ public void traverseOperation(Syn2Operation original, Syn2Operation updated) {
+ if (original == null && updated == null)
+ return;
+ if (!visitor.visitOperation(original, updated))
+ return;
+ if (original == null || updated == null)
+ return;
+ 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(original.getParameters(), updated.getParameters(), diff);
+ for (CollectionDiff.MatchedPair pair : diff.getMatched()) {
+ visitor.visitOperationParametersItem(pair.getOriginal(), pair.getUpdated());
+ if (pair.getOriginal() != null && pair.getUpdated() != null) {
+ traverse(pair.getOriginal(), pair.getUpdated());
+ }
+ }
+ }
+ }
+
+ 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
new file mode 100644
index 000000000..c2387eef7
--- /dev/null
+++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/v2/visitors/Syn2DiffVisitor.java
@@ -0,0 +1,240 @@
+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 java.util.List;
+import java.util.Map;
+
+public abstract class Syn2DiffVisitor {
+
+ 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(List diff) {
+ }
+
+ public void visitDocumentItemsItem(SynItem original, SynItem updated) {
+ }
+
+ public void diffDocumentTags(List diff) {
+ }
+
+ public void visitDocumentWebhook(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 diff) {
+ }
+
+ public void visitSchemaProperty(BooleanSchemaUnion original, BooleanSchemaUnion updated) {
+ }
+
+ public void diffSchemaAllOf(List diff) {
+ }
+
+ public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) {
+ }
+
+ public void diffSchemaDefinitions(Map diff) {
+ }
+
+ public void visitSchemaDefinition(BooleanSchemaUnion original, BooleanSchemaUnion updated) {
+ }
+
+ public void diffSchemaNestedSchemas(Map diff) {
+ }
+
+ public void visitSchemaNestedSchema(SchemaOrBoolean original, SchemaOrBoolean updated) {
+ }
+
+ public void diffSchemaComposedSchemas(List diff) {
+ }
+
+ public void visitSchemaComposedSchemasItem(SchemaOrBoolean original, SchemaOrBoolean updated) {
+ }
+
+ public void diffSchemaMinLength(Integer original, Integer updated) {
+ }
+
+ public void diffSchemaMaxLength(Integer original, Integer updated) {
+ }
+
+ public void diffSchemaEnum(List diff) {
+ }
+
+ 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/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..abe40943e
--- /dev/null
+++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/AbstractDiffTraverser.java
@@ -0,0 +1,51 @@
+package io.test.synthetic.visitors.diff;
+
+import io.test.synthetic.Any;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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 {
+
+ protected final V visitor;
+ protected final PairingStrategyProvider pairingProvider;
+
+ @SuppressWarnings("unchecked")
+ protected AbstractDiffTraverser(V visitor) {
+ this.visitor = visitor;
+ this.pairingProvider = (PairingStrategyProvider ) new DefaultPairingStrategyProvider();
+ }
+
+ protected AbstractDiffTraverser(V visitor, PairingStrategyProvider pairingProvider) {
+ this.visitor = visitor;
+ this.pairingProvider = pairingProvider;
+ }
+
+ /**
+ * 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) {
+ }
+
+ protected pairMap(String propertyName, Map strategy = pairingProvider.getMapStrategy(propertyName);
+ return strategy.pair(original, updated);
+ }
+
+ protected pairList(String propertyName, List 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/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..a68eca8f4
--- /dev/null
+++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/CollectionDiff.java
@@ -0,0 +1,116 @@
+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,
+ * or matched between the original and updated collections.
+ *
+ * @param
+ * the pairing key type (e.g., Integer for index-based pairing)
+ * @param {
+
+ CollectionDiff pair(List
+ * the pairing key type (e.g., String for key-based pairing)
+ * @param {
+
+ CollectionDiff pair(Map {
+
+ getMapStrategy(String propertyName);
+
+ getListStrategy(String propertyName);
+}