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 diff --git a/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java b/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java index da237a875..6caf78b75 100644 --- a/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java +++ b/generator/src/main/java/io/apitomy/umg/UnifiedModelGenerator.java @@ -58,6 +58,8 @@ 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.CreateDiffVisitorsStage; import io.apitomy.umg.pipe.java.CreateTraversersStage; import io.apitomy.umg.pipe.java.CreateCollectionUnionValuesStage; import io.apitomy.umg.pipe.java.CreatePrimitiveUnionValuesStage; @@ -163,6 +165,8 @@ 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()); 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..dcb7b87c3 --- /dev/null +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffTraversersStage.java @@ -0,0 +1,381 @@ +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.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.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.CodeGenContext; +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 the + * generated per-spec DiffVisitor's typed methods. + */ +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(); + + // 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.addTypeVariable("P"); + classSource.setSuperType("AbstractDiffTraverser>"); + + // 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 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<>(); + + createTraverseDispatch(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); + } + } + }); + + // 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); + } + + private void createTraverseDispatch(SpecificationVersion specVer, JavaClassSource classSource, + java.util.Set createdMethods) { + createdMethods.add("traverse"); + + String anyFQN = getState().getConfig().getRootNamespace() + ".Any"; + classSource.addImport(anyFQN); + + MethodSource method = classSource.addMethod() + .setName("traverse") + .setReturnTypeVoid() + .setPublic(); + method.addParameter("Any", "original"); + method.addParameter("Any", "updated"); + method.addAnnotation(Override.class); + + BodyBuilder body = new BodyBuilder(); + 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);"); + } + + // 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); + 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 entityName = entityModel.getName(); + String methodName = "traverse" + entityName; + + MethodSource method = classSource.addMethod() + .setName(methodName) + .setReturnTypeVoid() + .setPublic(); + method.addParameter(entityType, "original"); + method.addParameter(entityType, "updated"); + + Collection allProperties = + getState().getConceptIndex().getAllEntityProperties(entityModel); + + JavaTypeFactory jtf = getJavaTypeFactory(); + BodyBuilder body = new BodyBuilder(); + + body.append("if (original == null && updated == null) 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) { + PropertyModel property = propWithOrigin.getProperty(); + if (isStarProperty(property) || isRegexProperty(property)) { + continue; + } + + String getter = new GetterMethod(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)) { + 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(" traverse(original.${getter}(), updated.${getter}());"); + body.append("}"); + } else if (isEntityList(property) || isUnionList(property)) { + 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}(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 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 new file mode 100644 index 000000000..99f375f5a --- /dev/null +++ b/generator/src/main/java/io/apitomy/umg/pipe/java/CreateDiffVisitorsStage.java @@ -0,0 +1,259 @@ +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.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; +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); + + classSource.addTypeVariable("P"); + + 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); + }); + + // 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); + } + + 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); + + 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(""); + + String visitMethodName = "visit" + entityName + fieldSuffix + "Item"; + MethodSource visitMethod = classSource.addMethod() + .setName(visitMethodName) + .setReturnTypeVoid() + .setPublic(); + 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); + classSource.addImport(java.util.Map.class); + + 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 singularSuffix = singularize(fieldSuffix); + String visitMethodName = "visit" + entityName + singularSuffix; + MethodSource visitMethod = classSource.addMethod() + .setName(visitMethodName) + .setReturnTypeVoid() + .setPublic(); + visitMethod.addParameter(valueJt.toJavaTypeString(), "original"); + visitMethod.addParameter(valueJt.toJavaTypeString(), "updated"); + 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" + */ + private String fieldMethodSuffix(PropertyModel property) { + String name = property.getName(); + if (name.startsWith("$")) { + return name; + } + 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/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java b/generator/src/main/java/io/apitomy/umg/pipe/java/LoadBaseClassesStage.java index 64908ac2b..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 @@ -38,6 +38,12 @@ 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.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.union.ListUnionValueImpl", "io.apitomy.umg.base.union.MapUnionValueImpl", "io.apitomy.umg.base.union.EntityListUnionValueImpl", @@ -64,6 +70,10 @@ 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.PairingStrategyProvider", + "io.apitomy.umg.base.union.Union", "io.apitomy.umg.base.union.UnionValue" ); 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/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..425817bed --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/AbstractDiffTraverser.java @@ -0,0 +1,50 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.List; +import java.util.Map; + +import io.apitomy.umg.base.Any; + +/** + * 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 { + + 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 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); + 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 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(); + } + + /** + * 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; + + 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/DefaultListPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultListPairingStrategy.java new file mode 100644 index 000000000..9fbbbf744 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultListPairingStrategy.java @@ -0,0 +1,25 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Default list pairing strategy — pairs entries by index. + */ +public class DefaultListPairingStrategy implements ListPairingStrategy { + + @Override + public CollectionDiff pair(List original, List updated) { + return CollectionDiff.pairByKey(toPairingKeyMap(original), toPairingKeyMap(updated)); + } + + private static Map toPairingKeyMap(List list) { + if (list == null) return new LinkedHashMap<>(); + Map res = new LinkedHashMap<>(); + for (int i = 0; i < list.size(); i++) { + res.put(new DefaultPairingKey(i), list.get(i)); + } + return res; + } +} diff --git a/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultMapPairingStrategy.java b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultMapPairingStrategy.java new file mode 100644 index 000000000..de2a7ecdd --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/DefaultMapPairingStrategy.java @@ -0,0 +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 DefaultMapPairingStrategy implements MapPairingStrategy { + + @Override + 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/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 new file mode 100644 index 000000000..9340ccb28 --- /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 DefaultMapPairingStrategy<>(); + } + + @Override + public ListPairingStrategy getListStrategy(String propertyName) { + return new DefaultListPairingStrategy<>(); + } +} 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..ae0b7f1a9 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/ListPairingStrategy.java @@ -0,0 +1,14 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.List; + +/** + * 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 { + + 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..1a16710d5 --- /dev/null +++ b/generator/src/main/resources/base/io/apitomy/umg/base/visitors/diff/MapPairingStrategy.java @@ -0,0 +1,14 @@ +package io.apitomy.umg.base.visitors.diff; + +import java.util.Map; + +/** + * 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 { + + CollectionDiff pair(Map original, Map updated); +} 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..01e4a7dd6 --- /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 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 original, List updated, CollectionDiff diff) { + } + + public void visitDocumentItemsItem(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(Map original, Map updated, + CollectionDiff diff) { + } + + public void visitSchemaProperty(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaAllOf(List original, List updated, + CollectionDiff diff) { + } + + public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaDefinitions(Map original, Map updated, + CollectionDiff diff) { + } + + public void visitSchemaDefinition(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaNestedSchemas(Map original, Map updated, + CollectionDiff diff) { + } + + public void visitSchemaNestedSchema(SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public void diffSchemaComposedSchemas(List original, List updated, + CollectionDiff 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 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(List original, List updated, + CollectionDiff 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 original, List updated, CollectionDiff diff) { + } + + public void visitDocumentItemsItem(SynItem original, SynItem updated) { + } + + public void diffDocumentTags(List original, List updated) { + } + + public void diffDocumentMetadata(Map original, Map updated) { + } + + public void diffDocumentWebhooks(Map original, Map updated, + CollectionDiff 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 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(Map original, Map updated, + CollectionDiff diff) { + } + + public void visitSchemaProperty(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaAllOf(List original, List updated, + CollectionDiff diff) { + } + + public void visitSchemaAllOfItem(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaDefinitions(Map original, Map updated, + CollectionDiff diff) { + } + + public void visitSchemaDefinition(BooleanSchemaUnion original, BooleanSchemaUnion updated) { + } + + public void diffSchemaNestedSchemas(Map original, Map updated, + CollectionDiff diff) { + } + + public void visitSchemaNestedSchema(SchemaOrBoolean original, SchemaOrBoolean updated) { + } + + public void diffSchemaComposedSchemas(List original, List updated, + CollectionDiff 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 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(List original, List updated, + CollectionDiff 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 + * the spec-version-specific DiffVisitor subclass + */ +public class AbstractDiffTraverser { + + 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 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); + 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 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(); + } + + /** + * 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; + + 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/DefaultListPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultListPairingStrategy.java new file mode 100644 index 000000000..07c079a08 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultListPairingStrategy.java @@ -0,0 +1,26 @@ +package io.test.synthetic.visitors.diff; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Default list pairing strategy — pairs entries by index. + */ +public class DefaultListPairingStrategy implements ListPairingStrategy { + + @Override + public CollectionDiff pair(List original, List updated) { + return CollectionDiff.pairByKey(toPairingKeyMap(original), toPairingKeyMap(updated)); + } + + private static Map toPairingKeyMap(List list) { + if (list == null) + return new LinkedHashMap<>(); + Map res = new LinkedHashMap<>(); + for (int i = 0; i < list.size(); i++) { + res.put(new DefaultPairingKey(i), list.get(i)); + } + return res; + } +} diff --git a/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultMapPairingStrategy.java b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultMapPairingStrategy.java new file mode 100644 index 000000000..74c710be0 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/DefaultMapPairingStrategy.java @@ -0,0 +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 DefaultMapPairingStrategy implements MapPairingStrategy { + + @Override + 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/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 new file mode 100644 index 000000000..a2e04f5aa --- /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 DefaultMapPairingStrategy<>(); + } + + @Override + public ListPairingStrategy getListStrategy(String propertyName) { + return new DefaultListPairingStrategy<>(); + } +} 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..8bd8b609b --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/ListPairingStrategy.java @@ -0,0 +1,16 @@ +package io.test.synthetic.visitors.diff; + +import java.util.List; + +/** + * 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 { + + 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..457081a49 --- /dev/null +++ b/generator/src/test/resources/io/apitomy/umg/synthetic/expected/io/test/synthetic/visitors/diff/MapPairingStrategy.java @@ -0,0 +1,16 @@ +package io.test.synthetic.visitors.diff; + +import java.util.Map; + +/** + * 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 { + + CollectionDiff pair(Map original, Map updated); +} 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..e33af1e1a --- /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); +}