package com.deepoove.swagger.diff.compare; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.deepoove.swagger.diff.model.ElProperty; import io.swagger.models.ArrayModel; import io.swagger.models.Model; import io.swagger.models.RefModel; import io.swagger.models.properties.ArrayProperty; import io.swagger.models.properties.Property; import io.swagger.models.properties.RefProperty; import io.swagger.models.properties.StringProperty; /** * compare two model * * @author Sayi * @version */ public class ModelDiff { private List<ElProperty> increased; private List<ElProperty> missing; private List<ElProperty> changed; Map<String, Model> oldDedinitions; Map<String, Model> newDedinitions; private ModelDiff() { increased = new ArrayList<ElProperty>(); missing = new ArrayList<ElProperty>(); changed = new ArrayList<ElProperty>(); } public static ModelDiff buildWithDefinition(Map<String, Model> left, Map<String, Model> right) { ModelDiff diff = new ModelDiff(); diff.oldDedinitions = left; diff.newDedinitions = right; return diff; } public ModelDiff diff(Model leftModel, Model rightModel) { return this.diff(leftModel, rightModel, null, new HashSet<Model>()); } public ModelDiff diff(Model leftModel, Model rightModel, String parentEl) { return this.diff(leftModel, rightModel, parentEl, new HashSet<Model>()); } public ModelDiff diff(Property leftProperty, Property rightProperty) { return this.diff(findModel(leftProperty, oldDedinitions), findModel(rightProperty, newDedinitions)); } private ModelDiff diff(Model leftInputModel, Model rightInputModel, String parentEl, Set<Model> visited) { // Stop recursing if both models are null // OR either model is already contained in the visiting history if ((null == leftInputModel && null == rightInputModel) || visited.contains(leftInputModel) || visited.contains(rightInputModel)) { return this; } Model leftModel = isModelReference(leftInputModel) ? findReferenceModel(leftInputModel, oldDedinitions) : leftInputModel; Model rightModel = isModelReference(rightInputModel) ? findReferenceModel(rightInputModel, newDedinitions) : rightInputModel; Map<String, Property> leftProperties = null == leftModel ? null : leftModel.getProperties(); Map<String, Property> rightProperties = null == rightModel ? null : rightModel.getProperties(); // Diff the properties MapKeyDiff<String, Property> propertyDiff = MapKeyDiff.diff(leftProperties, rightProperties); increased.addAll(convert2ElPropertys(propertyDiff.getIncreased(), parentEl)); missing.addAll(convert2ElPropertys(propertyDiff.getMissing(), parentEl)); // Recursively find the diff between properties List<String> sharedKey = propertyDiff.getSharedKey(); sharedKey.stream().forEach((key) -> { Property left = leftProperties.get(key); Property right = rightProperties.get(key); Model leftSubModel = findModel(left, oldDedinitions); Model rightSubModel = findModel(left, newDedinitions); if (leftSubModel != null || rightSubModel != null) { diff(leftSubModel, rightSubModel, buildElString(parentEl, key), copyAndAdd(visited, leftModel, rightModel)); } else if (left != null && right != null && !left.equals(right)) { // Add a changed ElProperty if not a Reference // Useless changed.add(addChangeMetadata(convert2ElProperty(key, parentEl, left), left, right)); } }); return this; } private Collection<? extends ElProperty> convert2ElPropertys(Map<String, Property> propMap, String parentEl) { List<ElProperty> result = new ArrayList<ElProperty>(); if (null == propMap) return result; for (Entry<String, Property> entry : propMap.entrySet()) { // TODO Recursively get the properties result.add(convert2ElProperty(entry.getKey(), parentEl, entry.getValue())); } return result; } private String buildElString(String parentEl, String propName) { return null == parentEl ? propName : (parentEl + "." + propName); } private ElProperty convert2ElProperty(String propName, String parentEl, Property property) { ElProperty pWithPath = new ElProperty(); pWithPath.setProperty(property); pWithPath.setEl(buildElString(parentEl, propName)); return pWithPath; } private ElProperty addChangeMetadata(ElProperty diffProperty, Property left, Property right) { diffProperty.setTypeChange(!left.getType().equalsIgnoreCase(right.getType())); List<String> leftEnums = enumValues(left); List<String> rightEnums = enumValues(right); if (!leftEnums.isEmpty() && !rightEnums.isEmpty()) { ListDiff<String> enumDiff = ListDiff.diff(leftEnums, rightEnums, (t, enumVal) -> { for (String value : t) { if (enumVal.equalsIgnoreCase(value)) { return value; } } return null; }); diffProperty.setNewEnums(enumDiff.getIncreased() != null && !enumDiff.getIncreased().isEmpty()); diffProperty.setRemovedEnums(enumDiff.getMissing() != null && !enumDiff.getMissing().isEmpty()); } return diffProperty; } @SuppressWarnings("unchecked") private <T> Set<T> copyAndAdd(Set<T> set, T... add) { Set<T> newSet = new HashSet<T>(set); newSet.addAll(Arrays.asList(add)); return newSet; } private List<String> enumValues(Property prop) { if (prop instanceof StringProperty && ((StringProperty) prop).getEnum() != null) { return ((StringProperty) prop).getEnum(); } else { return new ArrayList<>(); } } private Model findModel(Property property, Map<String, Model> modelMap) { String modelName = null; if (property instanceof RefProperty) { modelName = ((RefProperty) property).getSimpleRef(); } else if (property instanceof ArrayProperty) { Property arrayType = ((ArrayProperty) property).getItems(); if (arrayType instanceof RefProperty) { modelName = ((RefProperty) arrayType).getSimpleRef(); } } return modelName == null ? null : modelMap.get(modelName); } private boolean isModelReference(Model model) { return model instanceof RefModel || model instanceof ArrayModel; } private Model findReferenceModel(Model model, Map<String, Model> modelMap) { String modelName = null; if (model instanceof RefModel) { modelName = ((RefModel) model).getSimpleRef(); } else if (model instanceof ArrayModel) { Property arrayType = ((ArrayModel) model).getItems(); if (arrayType instanceof RefProperty) { modelName = ((RefProperty) arrayType).getSimpleRef(); } } return modelName == null ? null : modelMap.get(modelName); } public List<ElProperty> getIncreased() { return increased; } public void setIncreased(List<ElProperty> increased) { this.increased = increased; } public List<ElProperty> getMissing() { return missing; } public void setMissing(List<ElProperty> missing) { this.missing = missing; } public List<ElProperty> getChanged() { return changed; } public void setChanged(List<ElProperty> changed) { this.changed = changed; } }