You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/06/12 18:17:22 UTC

[isis] 02/02: ISIS-2738: Implement MM diff reporting

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 3d255893d957c27910a322addbfbdd6c9508e767
Author: Andi Huber <ah...@apache.org>
AuthorDate: Sat Jun 12 20:17:05 2021 +0200

    ISIS-2738: Implement MM diff reporting
---
 .../applib/services/metamodel/_DiffExport.java     | 335 ++++++++++++++++++++-
 1 file changed, 331 insertions(+), 4 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/metamodel/_DiffExport.java b/api/applib/src/main/java/org/apache/isis/applib/services/metamodel/_DiffExport.java
index 5d5e38c..caba049 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/metamodel/_DiffExport.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/metamodel/_DiffExport.java
@@ -18,24 +18,351 @@
  */
 package org.apache.isis.applib.services.metamodel;
 
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.assertions._Assert;
+import org.apache.isis.commons.internal.collections._Sets;
+import org.apache.isis.commons.internal.collections._Streams;
+import org.apache.isis.schema.metamodel.v2.DomainClassDto;
+import org.apache.isis.schema.metamodel.v2.Facet;
+import org.apache.isis.schema.metamodel.v2.FacetAttr;
+import org.apache.isis.schema.metamodel.v2.FacetHolder.Facets;
+import org.apache.isis.schema.metamodel.v2.Member;
 import org.apache.isis.schema.metamodel.v2.MetamodelDto;
 
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 import lombok.experimental.UtilityClass;
 
 @UtilityClass
 class _DiffExport {
-
+    
     StringBuilder toDiff(
             MetamodelDto leftMetamodelDto,
             MetamodelDto rightMetamodelDto) {
+
+        val leftTypesById = new TreeMap<String, DomainClassDto>();
+        val rightTypesById = new TreeMap<String, DomainClassDto>();
+        
+        val facetsById = new TreeMap<String, Facet>();
+        final Consumer<? super Facet> facetCollector = facet->facetsById.put(facet.getId(), facet);
+        
+        visitAllFacets(leftMetamodelDto, facetCollector);
+        visitAllFacets(rightMetamodelDto, facetCollector);
+        
         val sb = new StringBuilder();
+        
+        // -- type intersection
+        
+        val leftTypes = streamTypes(leftMetamodelDto)
+                .peek(type->leftTypesById.put(type.getId(), type))
+                .map(DomainClassDto::getId)
+                .collect(Collectors.toSet());
+        
+        val rightTypes = streamTypes(rightMetamodelDto)
+                .peek(type->rightTypesById.put(type.getId(), type))
+                .map(DomainClassDto::getId)
+                .collect(Collectors.toSet());
+        
+        val leftNotInRight = _Sets.minus(leftTypes, rightTypes);
+        val rightNotInLeft = _Sets.minus(rightTypes, leftTypes);
+        
+        leftNotInRight
+            .stream()
+            .forEach(typeId->sb.append(LEFT_SYMBOL).append(" ").append(typeId).append("\n"));
+        rightNotInLeft
+            .stream()
+            .forEach(typeId->sb.append(RIGHT_SYMBOL).append(" ").append(typeId).append("\n"));
 
-//        metamodelDto
-//            .getDomainClassDto()
-//            .forEach(typeDto->toAscii(typeDto, sb));
+        val inLeftAndRight = _Sets.intersect(leftTypes, rightTypes);
+                
+        val leftTypeIntersection = inLeftAndRight
+                .stream()
+                .sorted()
+                .map(leftTypesById::get)
+                .collect(Can.toCan());
+        
+        val rightTypeIntersection = inLeftAndRight
+                .stream()
+                .sorted()
+                .map(rightTypesById::get)
+                .collect(Can.toCan());
+        
+        // -- member intersection
+        
+        val leftMembersByKey = new TreeMap<String, Member>();
+        val rightMembersByKey = new TreeMap<String, Member>();
+        
+        leftTypeIntersection
+        .stream()
+        .forEach(type->streamMembers(type)
+                .forEach(m->{
+                    val key = memberKey(type, m);
+                    leftMembersByKey.put(key, m);
+                    m.setId(key); // rewrite id with key (that includes type id)
+                }));
+        
+        rightTypeIntersection
+        .stream()
+        .forEach(type->streamMembers(type)
+                .forEach(m->{
+                    val key = memberKey(type, m);
+                    rightMembersByKey.put(key, m);
+                    m.setId(key); // rewrite id with key (that includes type id)
+                }));
+        
+        val leftNotInRightM = _Sets.minus(leftMembersByKey.keySet(), rightMembersByKey.keySet());
+        val rightNotInLeftM = _Sets.minus(rightMembersByKey.keySet(), leftMembersByKey.keySet());
+        
+        leftNotInRightM
+            .stream()
+            .forEach(memberKey->sb.append(LEFT_SYMBOL).append(" ").append(memberKey).append("\n"));
+        rightNotInLeftM
+            .stream()
+            .forEach(memberKey->sb.append(RIGHT_SYMBOL).append(" ").append(memberKey).append("\n"));
+        
+        val inLeftAndRightM = _Sets.intersect(leftMembersByKey.keySet(), rightMembersByKey.keySet());
+        
+        val leftMemberIntersection = inLeftAndRightM
+                .stream()
+                .sorted()
+                .map(leftMembersByKey::get)
+                .collect(Can.toCan());
+        
+        val rightMemberIntersection = inLeftAndRightM
+                .stream()
+                .sorted()
+                .map(rightMembersByKey::get)
+                .collect(Can.toCan());
+        
+        // -- report facet differences
+        
+        facetsById
+        .values()
+        .forEach(facet->{
+            val diffModel = new DiffModel(f->f.getId().equals(facet.getId()), 
+                    leftTypeIntersection, rightTypeIntersection,
+                    leftMemberIntersection, rightMemberIntersection);
+            diff(diffModel, leftMetamodelDto, rightMetamodelDto);
 
+            if(diffModel.isEmpty()) {
+                return; // skip (suppress output)
+            }
+            
+            sb.append('[').append(facet.getId()).append(']').append("\n\n");
+            sb.append(diffModel.sb.toString());
+            sb.append("\n");
+            
+        });
+        
         return sb;
     }
     
+    // -- HELPER
+    
+    private final static String LEFT_SYMBOL = "L"; 
+    private final static String RIGHT_SYMBOL = "R";
+    private final static String DIFF_SYMBOL = "D";
+    
+    @RequiredArgsConstructor
+    private static class DiffModel {
+        final StringBuilder sb = new StringBuilder();
+        final Predicate<Facet> facetFilter;
+        final Can<DomainClassDto> leftIntersection;
+        final Can<DomainClassDto> rightIntersection;
+        final Can<Member> leftMemberIntersection;
+        final Can<Member> rightMemberIntersection;
+        long diffCout;
+        boolean isEmpty() {
+            return diffCout==0L;
+        }
+    }
+    
+    private void diff(DiffModel diffModel, 
+            MetamodelDto leftMetamodelDto,
+            MetamodelDto rightMetamodelDto) {
+        
+        // type level facets
+        diffModel.leftIntersection.zip(diffModel.rightIntersection, (leftType, rightType)->{
+            diffFacets(diffModel, leftType.getId(), leftType.getFacets(), rightType.getFacets());
+        });
+        
+        // member level facets
+        diffModel.leftMemberIntersection.zip(diffModel.rightMemberIntersection, (leftMember, rightMember)->{
+            diffFacets(diffModel, leftMember.getId(), leftMember.getFacets(), rightMember.getFacets());
+        });
+        
+    }
+    
+    private void diffFacets(DiffModel diffModel, String typeOrMemberId, 
+            Facets leftFacets, Facets rightFacets) {
+        
+        val sb = diffModel.sb;
+        val leftFacet = findFirstFacet(leftFacets, diffModel.facetFilter);
+        val rightFacet = findFirstFacet(rightFacets, diffModel.facetFilter);
+        
+        if(leftFacet.isPresent()) {
+            if(!rightFacet.isPresent()) {
+                sb.append(LEFT_SYMBOL).append(" ").append(typeOrMemberId).append("\n");
+                diffModel.diffCout++;
+            } else {
+                diffAttrs(diffModel, typeOrMemberId, leftFacet.get(), rightFacet.get());
+            }
+        } else {
+            if(rightFacet.isPresent()) {
+                sb.append(RIGHT_SYMBOL).append(" ").append(typeOrMemberId).append("\n");
+                diffModel.diffCout++;
+            } else {
+                // skip (absent in both)
+            }
+        }
+    }
+    
+    private void diffAttrs(DiffModel diffModel, String typeOrMemberId, Facet leftFacet, Facet rightFacet) {
+        
+        val sb = diffModel.sb;
+        val leftAttrByName = new TreeMap<String, FacetAttr>();
+        val rightAttrByName = new TreeMap<String, FacetAttr>();
+
+        val leftAttrNames = streamFacetAttr(leftFacet)
+                .peek(attr->leftAttrByName.put(attr.getName(), attr))
+                .map(FacetAttr::getName)
+                .collect(Collectors.toSet());
+        
+        val rightAttrNames = streamFacetAttr(rightFacet)
+                .peek(attr->rightAttrByName.put(attr.getName(), attr))
+                .map(FacetAttr::getName)
+                .collect(Collectors.toSet());
+        
+        val leftNotInRight = _Sets.minus(leftAttrNames, rightAttrNames);
+        val rightNotInLeft = _Sets.minus(rightAttrNames, leftAttrNames);
+        
+        leftNotInRight
+            .stream()
+            .peek(__->diffModel.diffCout++)
+            .forEach(attrName->sb.append(LEFT_SYMBOL)
+                    .append(" ").append(typeOrMemberId).append(" ").append(attrName)
+                    .append(" ").append(leftAttrByName.get(attrName).getValue())
+                    .append("\n"));
+        rightNotInLeft
+            .stream()
+            .peek(__->diffModel.diffCout++)
+            .forEach(attrName->sb.append(RIGHT_SYMBOL)
+                    .append(" ").append(typeOrMemberId).append(" ").append(attrName)
+                    .append(" ").append(rightAttrByName.get(attrName).getValue())
+                    .append("\n"));
+
+        val inLeftAndRight = _Sets.intersect(leftAttrNames, rightAttrNames);
+                
+        val leftAttrIntersection = inLeftAndRight
+                .stream()
+                .sorted()
+                .map(leftAttrByName::get)
+                .collect(Can.toCan());
+        
+        val rightAttrIntersection = inLeftAndRight
+                .stream()
+                .sorted()
+                .map(rightAttrByName::get)
+                .collect(Can.toCan());
+        
+     
+        leftAttrIntersection.zip(rightAttrIntersection, (leftAttr, rightAttr)->{
+            _Assert.assertEquals(leftAttr.getName(), rightAttr.getName());
+            if(Objects.equals(leftAttr.getValue(), rightAttr.getValue())) {
+                return; // skip (same attr values)
+            }
+            
+            sb.append(DIFF_SYMBOL)
+            .append(" ").append(typeOrMemberId).append(" ").append(leftAttr.getName())
+            .append(" ").append(leftAttr.getValue()).append(" <-> ").append(rightAttr.getValue())
+            .append("\n");
+            
+            diffModel.diffCout++;
+        });
+        
+        
+    }
+
+    private Stream<DomainClassDto> streamTypes(MetamodelDto mmDto) {
+        return mmDto.getDomainClassDto()
+                .stream()
+                .sorted((a, b)->a.getId().compareTo(b.getId()));
+    }
+    
+    private Stream<Member> streamMembers(DomainClassDto typeDto) {
+
+        return _Streams.concat(
+        
+            Optional.ofNullable(typeDto.getProperties())
+            .map(props->props.getProp())
+            .map(List::stream)
+            .orElse(Stream.empty())
+            .sorted((a, b)->a.getId().compareTo(b.getId())),
+            
+            Optional.ofNullable(typeDto.getCollections())
+            .map(colls->colls.getColl())
+            .map(List::stream)
+            .orElse(Stream.empty())
+            .sorted((a, b)->a.getId().compareTo(b.getId())),
+
+            Optional.ofNullable(typeDto.getActions())
+            .map(acts->acts.getAct())
+            .map(List::stream)
+            .orElse(Stream.empty())
+            .sorted((a, b)->a.getId().compareTo(b.getId()))
+        );
+
+    }
+    
+    private Optional<Facet> findFirstFacet(Facets x, Predicate<Facet> filter) {
+        return Optional.ofNullable(x)
+                .map(Facets::getFacet)
+                .map(List::stream)
+                .orElse(Stream.empty())
+                .filter(filter)
+                .findFirst();
+    }
+    
+    private Stream<Facet> streamFacets(DomainClassDto x) {
+        return Optional.ofNullable(x.getFacets())
+                .map(Facets::getFacet)
+                .map(List::stream)
+                .orElse(Stream.empty());
+    }
+    
+    private Stream<Facet> streamFacets(Member x) {
+        return Optional.ofNullable(x.getFacets())
+                .map(Facets::getFacet)
+                .map(List::stream)
+                .orElse(Stream.empty());
+    }
+    
+    private Stream<FacetAttr> streamFacetAttr(Facet x) {
+        return Optional.ofNullable(x.getAttr())
+                .map(List::stream)
+                .orElse(Stream.empty())
+                .sorted((a, b)->a.getName().compareTo(b.getName()));
+    }
+
+    private String memberKey(DomainClassDto type, Member member) {
+        return type.getId() + "#" + member.getId();
+    }
+    
+    private void visitAllFacets(MetamodelDto mmDto, final Consumer<? super Facet> onFacet) {
+        streamTypes(mmDto)
+        .peek(x->streamFacets(x).forEach(onFacet))
+        .flatMap(_DiffExport::streamMembers)
+        .flatMap(_DiffExport::streamFacets)
+        .forEach(onFacet);
+    }
+    
 }