From 50dbc9bda12336d65f5d0768405079593e34e729 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 25 Jul 2020 08:38:45 +1000 Subject: [PATCH] significant work on tests to support version comparison --- .../comparison/CanonicalResourceComparer.java | 29 ++++ .../r5/comparison/CodeSystemComparer.java | 13 ++ .../r5/comparison/ComparisonRenderer.java | 66 ++++++-- .../fhir/r5/comparison/ComparisonSession.java | 88 ++++++---- .../fhir/r5/comparison/ProfileComparer.java | 154 +++++++++++------- .../fhir/r5/comparison/ResourceComparer.java | 136 +++++++++++++++- .../fhir/r5/comparison/StructuralMatch.java | 17 ++ .../fhir/r5/comparison/ValueSetComparer.java | 34 +++- .../comparison/tests/ComparisonTests.java | 4 +- 9 files changed, 436 insertions(+), 105 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java index 2f1ba72b9..a27616c2f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Set; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.utilities.Utilities; @@ -81,6 +82,34 @@ public abstract class CanonicalResourceComparer extends ResourceComparer { this.intersection = intersection; } + @Override + protected String toTable() { + String s = null; + if (left != null && right != null && !left.getUrl().equals(right.getUrl())) { + s = ""+left.getUrl()+""+right.getUrl()+""; + } else if (left != null) { + s = ""+left.getUrl()+""; + } else { + s = ""+right.getUrl()+""; + } + s = s + "Comparison"; + s = s + ""+outcomeSummary()+""; + return ""+s+"\r\n"; + } + + protected boolean hasErrors() { + MessageCounts cnts = new MessageCounts(); + countMessages(cnts); + return cnts.getErrors() > 0; + } + + + @Override + protected void countMessages(MessageCounts cnts) { + for (StructuralMatch sm : metadata.values()) { + sm.countMessages(cnts); + } + } } public CanonicalResourceComparer(ComparisonSession session) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java index 03aa439de..fd0810f48 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java @@ -9,6 +9,7 @@ import java.util.Map; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; @@ -55,6 +56,18 @@ public class CodeSystemComparer extends CanonicalResourceComparer { protected String summary() { return "CodeSystem: "+left.present()+" vs "+right.present(); } + + @Override + protected String fhirType() { + return "CodeSystem"; + } + + @Override + protected void countMessages(MessageCounts cnts) { + super.countMessages(cnts); + combined.countMessages(cnts); + } + } private CodeSystem right; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java index 9a81af28b..eff1c21ef 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java @@ -2,6 +2,8 @@ package org.hl7.fhir.r5.comparison; import java.io.FileOutputStream; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -13,6 +15,7 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison; import org.hl7.fhir.r5.comparison.ProfileComparer.ProfileComparison; +import org.hl7.fhir.r5.comparison.ResourceComparer.PlaceHolderComparison; import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison; import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison; @@ -35,14 +38,16 @@ import org.hl7.fhir.utilities.xhtml.XhtmlComposer; public class ComparisonRenderer implements IEvaluationContext { - private IWorkerContext context; + private IWorkerContext contextLeft; + private IWorkerContext contextRight; private ComparisonSession session; private Map templates = new HashMap<>(); private String folder; - public ComparisonRenderer(IWorkerContext context, String folder, ComparisonSession session) { + public ComparisonRenderer(IWorkerContext contextLeft, IWorkerContext contextRight, String folder, ComparisonSession session) { super(); - this.context = context; + this.contextLeft = contextLeft; + this.contextRight = contextRight; this.folder = folder; this.session = session; } @@ -54,12 +59,13 @@ public class ComparisonRenderer implements IEvaluationContext { public void render() throws IOException { dumpBinaries(); StringBuilder b = new StringBuilder(); - - for (String id : sorted(session.getCompares().keySet())) { - ResourceComparison comp = session.getCompares().get(id); - renderComparison(id, comp); - b.append("
  • "+Utilities.escapeXml(comp.summary())+"
  • \r\n"); - } + b.append("\r\n"); + List list = sorted(session.getCompares().keySet()); + processList(list, b, "CodeSystem"); + processList(list, b, "ValueSet"); + processList(list, b, "StructureDefinition"); + b.append("
    \r\n"); + Map vars = new HashMap<>(); CodeSystemComparer cs = new CodeSystemComparer(session); vars.put("title", new StringType(session.getTitle())); @@ -69,6 +75,23 @@ public class ComparisonRenderer implements IEvaluationContext { TextFile.stringToFile(cnt, file("index.html")); } + private void processList(List list, StringBuilder b, String name) throws IOException { + // TODO Auto-generated method stub + boolean first = true; + for (String id : list) { + ResourceComparison comp = session.getCompares().get(id); + if (comp.fhirType().equals(name)) { + if (first) { + first = false; + b.append(""+name+"\r\n"); + } + renderComparison(id, comp); + b.append(comp.toTable()); + //"
  • "+Utilities.escapeXml(comp.summary())+"
  • \r\n" + } + } + } + private List sorted(Set keySet) { List list = new ArrayList<>(); list.addAll(keySet); @@ -77,8 +100,11 @@ public class ComparisonRenderer implements IEvaluationContext { } private void dumpBinaries() throws IOException { - for (String k : context.getBinaries().keySet()) { - TextFile.bytesToFile(context.getBinaries().get(k), Utilities.path(folder, k)); + for (String k : contextLeft.getBinaries().keySet()) { + TextFile.bytesToFile(contextLeft.getBinaries().get(k), Utilities.path(folder, k)); + } + for (String k : contextRight.getBinaries().keySet()) { + TextFile.bytesToFile(contextRight.getBinaries().get(k), Utilities.path(folder, k)); } } @@ -89,9 +115,23 @@ public class ComparisonRenderer implements IEvaluationContext { renderValueSet(id, (ValueSetComparison) comp); } else if (comp instanceof CodeSystemComparison) { renderCodeSystem(id, (CodeSystemComparison) comp); + } else if (comp instanceof PlaceHolderComparison) { + renderPlaceHolder(id, (PlaceHolderComparison) comp); } } + private void renderPlaceHolder(String id, PlaceHolderComparison comp) throws IOException { + String cnt = ""; + if (comp.getE() != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + comp.getE().printStackTrace(pw); + cnt = sw.toString(); + } + cnt = "
    "+cnt+"
    \r\n"; + TextFile.stringToFile(cnt, file(comp.getId()+".html")); + } + private void renderCodeSystem(String id, CodeSystemComparison comp) throws IOException { String template = templates.get("CodeSystem"); Map vars = new HashMap<>(); @@ -138,7 +178,7 @@ public class ComparisonRenderer implements IEvaluationContext { private void renderProfile(String id, ProfileComparison comp) throws IOException { String template = templates.get("Profile"); Map vars = new HashMap<>(); - ProfileComparer cs = new ProfileComparer(session, new ProfileUtilities(session.getContext(), null, session.getPkp())); + ProfileComparer cs = new ProfileComparer(session, new ProfileUtilities(session.getContextLeft(), null, session.getPkp()), new ProfileUtilities(session.getContextRight(), null, session.getPkp())); vars.put("left", new StringType(comp.getLeft().present())); vars.put("right", new StringType(comp.getRight().present())); vars.put("leftId", new StringType(comp.getLeft().getId())); @@ -155,7 +195,7 @@ public class ComparisonRenderer implements IEvaluationContext { } private String processTemplate(String template, String name, Map vars) { - LiquidEngine engine = new LiquidEngine(context, this); + LiquidEngine engine = new LiquidEngine(contextRight, this); LiquidDocument doc = engine.parse(template, name+".template"); return engine.evaluate(doc, Tuple.fromMap(vars), vars); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java index 0f563c1a5..1ce1f8915 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java @@ -25,24 +25,30 @@ import org.hl7.fhir.utilities.Utilities; public class ComparisonSession { private Map compares = new HashMap<>(); - private IWorkerContext context; + private IWorkerContext contextLeft; + private IWorkerContext contextRight; private String sessiondId; private int count; private boolean debug; private String title; private ProfileKnowledgeProvider pkp; - public ComparisonSession(IWorkerContext context, String title, ProfileKnowledgeProvider pkp) { + public ComparisonSession(IWorkerContext contextLeft, IWorkerContext contextRight, String title, ProfileKnowledgeProvider pkp) { super(); - this.context = context; + this.contextLeft = contextLeft; + this.contextRight = contextRight; this.sessiondId = UUID.randomUUID().toString().toLowerCase(); this.title = title; this.pkp = pkp; // debug = true; } - public IWorkerContext getContext() { - return context; + public IWorkerContext getContextLeft() { + return contextLeft; + } + + public IWorkerContext getContextRight() { + return contextRight; } public String getTitle() { @@ -50,11 +56,11 @@ public class ComparisonSession { } public ResourceComparison compare(String left, String right) throws DefinitionException, FHIRFormatError, IOException { - CanonicalResource l = (CanonicalResource) context.fetchResource(Resource.class, left); + CanonicalResource l = (CanonicalResource) contextLeft.fetchResource(Resource.class, left); if (l == null) { throw new DefinitionException("Unable to resolve "+left); } - CanonicalResource r = (CanonicalResource) context.fetchResource(Resource.class, right); + CanonicalResource r = (CanonicalResource) contextRight.fetchResource(Resource.class, right); if (r == null) { throw new DefinitionException("Unable to resolve "+right); } @@ -62,30 +68,54 @@ public class ComparisonSession { } public ResourceComparison compare(CanonicalResource left, CanonicalResource right) throws DefinitionException, FHIRFormatError, IOException { - String key = key(left.getUrl(), left.getVersion(), right.getUrl(), right.getVersion()); - if (compares.containsKey(key)) { - // if null then the comparison is in progress. - // this can happen when profiles refer to each other - return compares.get(key); - } - compares.put(key, null); - if (left instanceof CodeSystem && right instanceof CodeSystem) { - CodeSystemComparer cs = new CodeSystemComparer(this); - CodeSystemComparison csc = cs.compare((CodeSystem) left, (CodeSystem) right); + if (left != null && right != null) { + String key = key(left.getUrl(), left.getVersion(), right.getUrl(), right.getVersion()); + if (compares.containsKey(key)) { + // if null then the comparison is in progress. + // this can happen when profiles refer to each other + return compares.get(key); + } + compares.put(key, null); + try { + if (left instanceof CodeSystem && right instanceof CodeSystem) { + CodeSystemComparer cs = new CodeSystemComparer(this); + CodeSystemComparison csc = cs.compare((CodeSystem) left, (CodeSystem) right); + compares.put(key, csc); + return csc; + } else if (left instanceof ValueSet && right instanceof ValueSet) { + ValueSetComparer cs = new ValueSetComparer(this); + ValueSetComparison csc = cs.compare((ValueSet) left, (ValueSet) right); + compares.put(key, csc); + return csc; + } else if (left instanceof StructureDefinition && right instanceof StructureDefinition) { + ProfileComparer cs = new ProfileComparer(this, new ProfileUtilities(contextLeft, null, pkp), new ProfileUtilities(contextRight, null, pkp)); + ProfileComparison csc = cs.compare((StructureDefinition) left, (StructureDefinition) right); + compares.put(key, csc); + return csc; + } else { + throw new FHIRException("Unable to compare resources of type "+left.fhirType()+" and "+right.fhirType()); + } + } catch (Throwable e) { + ResourceComparer.PlaceHolderComparison csc = new ResourceComparer.PlaceHolderComparison(left, right, e); + compares.put(key, csc); + return csc; + } + } else if (left != null) { + String key = key(left.getUrl(), left.getVersion(), left.getUrl(), left.getVersion()); + if (compares.containsKey(key)) { + return compares.get(key); + } + ResourceComparer.PlaceHolderComparison csc = new ResourceComparer.PlaceHolderComparison(left, right); compares.put(key, csc); - return csc; - } else if (left instanceof ValueSet && right instanceof ValueSet) { - ValueSetComparer cs = new ValueSetComparer(this); - ValueSetComparison csc = cs.compare((ValueSet) left, (ValueSet) right); - compares.put(key, csc); - return csc; - } else if (left instanceof StructureDefinition && right instanceof StructureDefinition) { - ProfileComparer cs = new ProfileComparer(this, new ProfileUtilities(context, null, pkp)); - ProfileComparison csc = cs.compare((StructureDefinition) left, (StructureDefinition) right); - compares.put(key, csc); - return csc; + return csc; } else { - throw new FHIRException("Unable to compare "); + String key = key(right.getUrl(), right.getVersion(), right.getUrl(), right.getVersion()); + if (compares.containsKey(key)) { + return compares.get(key); + } + ResourceComparer.PlaceHolderComparison csc = new ResourceComparer.PlaceHolderComparison(left, right); + compares.put(key, csc); + return csc; } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ProfileComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ProfileComparer.java index 277d22b8b..676a533a2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ProfileComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ProfileComparer.java @@ -9,9 +9,11 @@ import java.util.List; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison; import org.hl7.fhir.r5.conformance.ProfileUtilities; import org.hl7.fhir.r5.conformance.ProfileUtilities.UnusedTracker; +import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.formats.IParser; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.Coding; @@ -63,16 +65,29 @@ public class ProfileComparer extends CanonicalResourceComparer { protected String summary() { return "Profile: "+left.present()+" vs "+right.present(); } + + @Override + protected String fhirType() { + return "StructureDefinition"; + } + @Override + protected void countMessages(MessageCounts cnts) { + super.countMessages(cnts); + combined.countMessages(cnts); + } + } - private ProfileUtilities utils; + private ProfileUtilities utilsLeft; + private ProfileUtilities utilsRight; - public ProfileComparer(ComparisonSession session, ProfileUtilities utils) { + public ProfileComparer(ComparisonSession session, ProfileUtilities utilsLeft, ProfileUtilities utilsRight) { super(session); - this.utils = utils; + this.utilsLeft = utilsLeft; + this.utilsRight = utilsRight; } @Override @@ -110,8 +125,8 @@ public class ProfileComparer extends CanonicalResourceComparer { comparePrimitives("baseDefinition", left.getBaseDefinitionElement(), right.getBaseDefinitionElement(), res.getMetadata(), IssueSeverity.ERROR, res); if (left.getType().equals(right.getType())) { - DefinitionNavigator ln = new DefinitionNavigator(session.getContext(), left); - DefinitionNavigator rn = new DefinitionNavigator(session.getContext(), right); + DefinitionNavigator ln = new DefinitionNavigator(session.getContextLeft(), left); + DefinitionNavigator rn = new DefinitionNavigator(session.getContextRight(), right); StructuralMatch sm = new StructuralMatch(ln.current(), rn.current()); compareElements(res, sm, ln.path(), null, ln, rn); res.combined = sm; @@ -122,9 +137,9 @@ public class ProfileComparer extends CanonicalResourceComparer { private void check(StructureDefinition sd, String name) { if (sd == null) throw new DefinitionException("No StructureDefinition provided ("+name+": "+sd.getName()+")"); - if (sd.getType().equals("Extension")) { - throw new DefinitionException("StructureDefinition is for an extension - use ExtensionComparer instead ("+name+": "+sd.getName()+")"); - } +// if (sd.getType().equals("Extension")) { +// throw new DefinitionException("StructureDefinition is for an extension - use ExtensionComparer instead ("+name+": "+sd.getName()+")"); +// } if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { throw new DefinitionException("StructureDefinition is not for an profile - can't be compared ("+name+": "+sd.getName()+")"); } @@ -143,10 +158,10 @@ public class ProfileComparer extends CanonicalResourceComparer { } // not allowed to be different: - ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path); - ruleEqual(comp, res, left.current().getMeaningWhenMissingElement(), right.current().getMeaningWhenMissingElement(), "meaningWhenMissing", path); - ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); +// ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path); +// ruleEqual(comp, res, left.current().getMeaningWhenMissingElement(), right.current().getMeaningWhenMissingElement(), "meaningWhenMissing", path); +// ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - this check belongs in the core +// ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); - so does this // we ignore slicing right now - we're going to clone the root one anyway, and then think about clones // simple stuff @@ -317,15 +332,15 @@ public class ProfileComparer extends CanonicalResourceComparer { } else if (vRight == null) { vm(IssueSeverity.ERROR, "Removed "+name, path, comp.getMessages(), res.getMessages()); } else if (!Base.compareDeep(vLeft, vRight, false)) { - vm(IssueSeverity.ERROR, name+" must be the same ("+toString(vLeft)+"/"+toString(vRight)+")", path, comp.getMessages(), res.getMessages()); + vm(IssueSeverity.ERROR, name+" must be the same ("+toString(vLeft, true)+"/"+toString(vRight, false)+")", path, comp.getMessages(), res.getMessages()); } } - private String toString(DataType val) throws IOException { + private String toString(DataType val, boolean left) throws IOException { if (val instanceof PrimitiveType) return "\"" + ((PrimitiveType) val).getValueAsString()+"\""; - IParser jp = session.getContext().newJsonParser(); + IParser jp = left ? session.getContextLeft().newJsonParser() : session.getContextRight().newJsonParser(); return jp.composeString(val, "value"); } @@ -468,13 +483,13 @@ public class ProfileComparer extends CanonicalResourceComparer { private Collection unionTypes(ProfileComparison comp, StructuralMatch res, String path, List left, List right) throws DefinitionException, IOException, FHIRFormatError { List result = new ArrayList(); for (TypeRefComponent l : left) - checkAddTypeUnion(comp, res, path, result, l); + checkAddTypeUnion(comp, res, path, result, l, session.getContextLeft()); for (TypeRefComponent r : right) - checkAddTypeUnion(comp, res, path, result, r); + checkAddTypeUnion(comp, res, path, result, r, session.getContextRight()); return result; } - private void checkAddTypeUnion(ProfileComparison comp, StructuralMatch res, String path, List results, TypeRefComponent nw) throws DefinitionException, IOException, FHIRFormatError { + private void checkAddTypeUnion(ProfileComparison comp, StructuralMatch res, String path, List results, TypeRefComponent nw, IWorkerContext ctxt) throws DefinitionException, IOException, FHIRFormatError { boolean pfound = false; boolean tfound = false; nw = nw.copy(); @@ -491,15 +506,15 @@ public class ProfileComparer extends CanonicalResourceComparer { ex.setProfile(null); } else { // both have profiles. Is one derived from the other? - StructureDefinition sdex = session.getContext().fetchResource(StructureDefinition.class, ex.getProfile().get(0).getValue()); - StructureDefinition sdnw = session.getContext().fetchResource(StructureDefinition.class, nw.getProfile().get(0).getValue()); + StructureDefinition sdex = ((IWorkerContext) ex.getUserData("ctxt")).fetchResource(StructureDefinition.class, ex.getProfile().get(0).getValue()); + StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getProfile().get(0).getValue()); if (sdex != null && sdnw != null) { - if (sdex == sdnw) { + if (sdex.getUrl().equals(sdnw.getUrl())) { pfound = true; - } else if (derivesFrom(sdex, sdnw)) { + } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData("ctxt")))) { ex.setProfile(nw.getProfile()); pfound = true; - } else if (derivesFrom(sdnw, sdex)) { + } else if (derivesFrom(sdnw, sdex, ctxt)) { pfound = true; } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) { ProfileComparison compP = (ProfileComparison) session.compare(sdex, sdnw); @@ -519,15 +534,15 @@ public class ProfileComparer extends CanonicalResourceComparer { ex.setTargetProfile(null); } else { // both have profiles. Is one derived from the other? - StructureDefinition sdex = session.getContext().fetchResource(StructureDefinition.class, ex.getTargetProfile().get(0).getValue()); - StructureDefinition sdnw = session.getContext().fetchResource(StructureDefinition.class, nw.getTargetProfile().get(0).getValue()); + StructureDefinition sdex = ((IWorkerContext) ex.getUserData("ctxt")).fetchResource(StructureDefinition.class, ex.getTargetProfile().get(0).getValue()); + StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getTargetProfile().get(0).getValue()); if (sdex != null && sdnw != null) { - if (sdex == sdnw) { + if (matches(sdex, sdnw)) { tfound = true; - } else if (derivesFrom(sdex, sdnw)) { + } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData("ctxt")))) { ex.setTargetProfile(nw.getTargetProfile()); tfound = true; - } else if (derivesFrom(sdnw, sdex)) { + } else if (derivesFrom(sdnw, sdex, ctxt)) { tfound = true; } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) { ProfileComparison compP = (ProfileComparison) session.compare(sdex, sdnw); @@ -540,17 +555,33 @@ public class ProfileComparer extends CanonicalResourceComparer { } } } - if (!tfound || !pfound) + if (!tfound || !pfound) { + nw.setUserData("ctxt", ctxt); results.add(nw); + } } - private boolean derivesFrom(StructureDefinition left, StructureDefinition right) { + private boolean matches(StructureDefinition s1, StructureDefinition s2) { + if (!s1.getUrl().equals(s2.getUrl())) { + return false; + } + if (s1.getDerivation() == TypeDerivationRule.SPECIALIZATION && s2.getDerivation() == TypeDerivationRule.SPECIALIZATION) { + return true; // arbitrary; we're just not interested in pursuing cross version differences + } + if (s1.hasVersion()) { + return s1.getVersion().equals(s2.getVersion()); + } else { + return !s2.hasVersion(); + } + } + + private boolean derivesFrom(StructureDefinition left, StructureDefinition right, IWorkerContext ctxt) { StructureDefinition sd = left; while (sd != null) { if (right.getUrl().equals(sd.getBaseDefinition())) { return true; } - sd = sd.hasBaseDefinition() ? session.getContext().fetchResource(StructureDefinition.class, sd.getBaseDefinition()) : null; + sd = sd.hasBaseDefinition() ? ctxt.fetchResource(StructureDefinition.class, sd.getBaseDefinition()) : null; } return false; } @@ -574,14 +605,14 @@ public class ProfileComparer extends CanonicalResourceComparer { pfound = true; c.setProfile(r.getProfile()); } else { - StructureDefinition sdl = resolveProfile(comp, res, path, l.getProfile().get(0).getValue(), comp.getLeft().getName()); - StructureDefinition sdr = resolveProfile(comp, res, path, r.getProfile().get(0).getValue(), comp.getRight().getName()); + StructureDefinition sdl = resolveProfile(comp, res, path, l.getProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft()); + StructureDefinition sdr = resolveProfile(comp, res, path, r.getProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight()); if (sdl != null && sdr != null) { if (sdl == sdr) { pfound = true; - } else if (derivesFrom(sdl, sdr)) { + } else if (derivesFrom(sdl, sdr, session.getContextLeft())) { pfound = true; - } else if (derivesFrom(sdr, sdl)) { + } else if (derivesFrom(sdr, sdl, session.getContextRight())) { c.setProfile(r.getProfile()); pfound = true; } else if (sdl.getType().equals(sdr.getType())) { @@ -601,14 +632,14 @@ public class ProfileComparer extends CanonicalResourceComparer { tfound = true; c.setTargetProfile(r.getTargetProfile()); } else { - StructureDefinition sdl = resolveProfile(comp, res, path, l.getTargetProfile().get(0).getValue(), comp.getLeft().getName()); - StructureDefinition sdr = resolveProfile(comp, res, path, r.getTargetProfile().get(0).getValue(), comp.getRight().getName()); + StructureDefinition sdl = resolveProfile(comp, res, path, l.getTargetProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft()); + StructureDefinition sdr = resolveProfile(comp, res, path, r.getTargetProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight()); if (sdl != null && sdr != null) { - if (sdl == sdr) { + if (matches(sdl, sdr)) { tfound = true; - } else if (derivesFrom(sdl, sdr)) { + } else if (derivesFrom(sdl, sdr, session.getContextLeft())) { tfound = true; - } else if (derivesFrom(sdr, sdl)) { + } else if (derivesFrom(sdr, sdl, session.getContextRight())) { c.setTargetProfile(r.getTargetProfile()); tfound = true; } else if (sdl.getType().equals(sdr.getType())) { @@ -713,8 +744,8 @@ public class ProfileComparer extends CanonicalResourceComparer { return true; } else { // ok, now we compare the value sets. This may be unresolvable. - ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet()); - ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet()); + ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), session.getContextLeft()); + ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), session.getContextRight()); if (lvs == null) { vm(IssueSeverity.ERROR, "Unable to resolve left value set "+left.getValueSet().toString()+" at "+path, path, comp.getMessages(), res.getMessages()); return true; @@ -739,6 +770,9 @@ public class ProfileComparer extends CanonicalResourceComparer { if (!lvs.getUrl().equals(rvs.getUrl())) { return false; } + if (isCore(lvs) && isCore(rvs)) { + return true; + } if (lvs.hasVersion()) { if (!lvs.getVersion().equals(rvs.getVersion())) { return false; @@ -749,6 +783,10 @@ public class ProfileComparer extends CanonicalResourceComparer { return true; } + private boolean isCore(ValueSet vs) { + return vs.getUrl().startsWith("http://hl7.org/fhir/ValueSet"); + } + private List intersectConstraints(String path, List left, List right) { List result = new ArrayList(); for (ElementDefinitionConstraintComponent l : left) { @@ -771,7 +809,9 @@ public class ProfileComparer extends CanonicalResourceComparer { if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity())) found = true; if (!found) { - vm(IssueSeverity.INFORMATION, "StructureDefinition "+comp.getLeft().getName()+" has a constraint that is removed in "+comp.getRight().getName()+" and it is uncertain whether they are compatible ("+l.getExpression()+")", path, comp.getMessages(), res.getMessages()); + if (!Utilities.existsInList(l.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) { + vm(IssueSeverity.INFORMATION, "StructureDefinition "+comp.getLeft().getName()+" has a constraint that is removed in "+comp.getRight().getName()+" and it is uncertain whether they are compatible ("+l.getExpression()+")", path, comp.getMessages(), res.getMessages()); + } } result.add(l); } @@ -781,14 +821,16 @@ public class ProfileComparer extends CanonicalResourceComparer { if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity())) found = true; if (!found) { - vm(IssueSeverity.INFORMATION, "StructureDefinition "+comp.getRight().getName()+" has added constraint that is not found in "+comp.getLeft().getName()+" and it is uncertain whether they are compatible ("+r.getExpression()+")", path, comp.getMessages(), res.getMessages()); + if (!Utilities.existsInList(r.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) { + vm(IssueSeverity.INFORMATION, "StructureDefinition "+comp.getRight().getName()+" has added constraint that is not found in "+comp.getLeft().getName()+" and it is uncertain whether they are compatible ("+r.getExpression()+")", path, comp.getMessages(), res.getMessages()); + } } } return result; } - private StructureDefinition resolveProfile(ProfileComparison comp, StructuralMatch res, String path, String url, String name) { - StructureDefinition sd = session.getContext().fetchResource(StructureDefinition.class, url); + private StructureDefinition resolveProfile(ProfileComparison comp, StructuralMatch res, String path, String url, String name, IWorkerContext ctxt) { + StructureDefinition sd = ctxt.fetchResource(StructureDefinition.class, url); if (sd == null) { ValidationMessage vm = vmI(IssueSeverity.WARNING, "Unable to resolve profile "+url+" in profile "+name, path); } @@ -809,8 +851,8 @@ public class ProfileComparer extends CanonicalResourceComparer { if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) union.setValueSet(left.getValueSet()); else { - ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet()); - ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet()); + ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), session.getContextLeft()); + ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), session.getContextRight()); if (lvs != null && rvs != null) { ValueSetComparison compP = (ValueSetComparison) session.compare(lvs, rvs); if (compP != null) { @@ -825,15 +867,15 @@ public class ProfileComparer extends CanonicalResourceComparer { return union; } - private ValueSet resolveVS(StructureDefinition ctxtLeft, String vsRef) { + private ValueSet resolveVS(StructureDefinition ctxtLeft, String vsRef, IWorkerContext ctxt) { if (vsRef == null) return null; - return session.getContext().fetchResource(ValueSet.class, vsRef); + return ctxt.fetchResource(ValueSet.class, vsRef); } public XhtmlNode renderStructure(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException { HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false, true); - gen.setTranslator(session.getContext().translator()); + gen.setTranslator(session.getContextRight().translator()); TableModel model = gen.initComparisonTable(corePath, id); genElementComp(null /* oome back to this later */, gen, model.getRows(), comp.combined, corePath, prefix, null, true); return gen.generate(model, prefix, 0, null); @@ -849,7 +891,7 @@ public class ProfileComparer extends CanonicalResourceComparer { rows.add(row); String path = combined.either().getPath(); row.setAnchor(path); - row.setColor(utils.getRowColor(combined.either(), false)); + row.setColor(utilsRight.getRowColor(combined.either(), false)); if (eitherHasSlicing(combined)) row.setLineColor(1); else if (eitherHasSliceName(combined)) @@ -895,17 +937,17 @@ public class ProfileComparer extends CanonicalResourceComparer { String leftColor = !combined.hasLeft() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null; String rightColor = !combined.hasRight() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null; if (combined.hasLeft()) { - nc = utils.genElementNameCell(gen, combined.getLeft(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName); + nc = utilsRight.genElementNameCell(gen, combined.getLeft(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName); } else { - nc = utils.genElementNameCell(gen, combined.getRight(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName); + nc = utilsRight.genElementNameCell(gen, combined.getRight(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName); } if (combined.hasLeft()) { - frame(utils.genElementCells(gen, combined.getLeft(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName, nc), leftColor); + frame(utilsRight.genElementCells(gen, combined.getLeft(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName, nc), leftColor); } else { frame(spacers(row, 4, gen), leftColor); } if (combined.hasRight()) { - frame(utils.genElementCells(gen, combined.getRight(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used, ref, sName, nc), rightColor); + frame(utilsRight.genElementCells(gen, combined.getRight(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used, ref, sName, nc), rightColor); } else { frame(spacers(row, 4, gen), rightColor); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ResourceComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ResourceComparer.java index 09595455c..28a84e423 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ResourceComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ResourceComparer.java @@ -3,6 +3,13 @@ package org.hl7.fhir.r5.comparison; import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; +import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison; +import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; @@ -14,8 +21,33 @@ import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class ResourceComparer { + + public static class MessageCounts { + private int errors; + private int warnings; + private int hints; + public int getErrors() { + return errors; + } + public int getWarnings() { + return warnings; + } + public int getHints() { + return hints; + } + public void error() { + errors++; + } + public void warning() { + warnings++; + } + public void hint() { + hints++; + } + } - public abstract class ResourceComparison { + + public static abstract class ResourceComparison { private String id; private String leftId; private String rightId; @@ -48,13 +80,115 @@ public class ResourceComparer { } protected abstract String summary(); + + protected abstract String fhirType(); + + protected abstract String toTable(); + + protected String outcomeSummary() { + MessageCounts cnts = new MessageCounts(); + countMessages(cnts); + return + Integer.toString(cnts.getErrors())+" "+Utilities.pluralize("Breaking Change", cnts.getErrors())+", "+ + Integer.toString(cnts.getWarnings())+" "+Utilities.pluralize("Change", cnts.getWarnings())+", "+ + Integer.toString(cnts.getHints())+" "+Utilities.pluralize("Note", cnts.getHints()); + } + + protected abstract void countMessages(MessageCounts cnts); } + + public static class PlaceHolderComparison extends ResourceComparison { + private CanonicalResource left; + private CanonicalResource right; + private Throwable e; + + public PlaceHolderComparison(CanonicalResource left, CanonicalResource right) { + super(left == null ? right.getId() : left.getId(), right == null ? left.getId() : right.getId()); + this.left = left; + this.right = right; + } + + public PlaceHolderComparison(CanonicalResource left, CanonicalResource right, Throwable e) { + super(left == null ? right.getId() : left.getId(), right == null ? left.getId() : right.getId()); + this.e = e; + this.left = left; + this.right = right; + } + + @Override + protected String abbreviation() { + CanonicalResource cr = left == null ? right : left; + if (cr instanceof CodeSystem) { + return "cs"; + } else if (cr instanceof ValueSet) { + return "vs"; + } else if (cr instanceof StructureDefinition) { + return "sd"; + } else { + return "xx"; + } + } + + @Override + protected String summary() { + if (e != null) { + return e.getMessage(); + } + CanonicalResource cr = left == null ? right : left; + return cr.fhirType()+(left == null ? " Added" : " Removed"); + } + + @Override + protected String fhirType() { + CanonicalResource cr = left == null ? right : left; + return cr.fhirType(); + } + + @Override + protected String toTable() { + String s = null; + String color = null; + if (left != null && right != null && !left.getUrl().equals(right.getUrl())) { + s = ""+left.getUrl()+""+right.getUrl()+""; + } else if (left != null) { + s = ""+left.getUrl()+""; + } else { + s = ""+right.getUrl()+""; + } + if (left == null) { + s = s + "Added"; + color = COLOR_NO_ROW_LEFT; + } else if (right == null) { + s = s + "Removed"; + color = COLOR_NO_ROW_RIGHT; + } else { + s = s + "Failed"; + color = COLOR_ISSUE; + } + s = s + ""+(e != null ? Utilities.escapeXml(e.getMessage()) : "")+""; + return ""+s+"\r\n"; + } + + public Throwable getE() { + return e; + } + + @Override + protected void countMessages(MessageCounts cnts) { + if (e != null) { + cnts.error(); + } + } + + } + public final static String COLOR_NO_ROW_LEFT = "#ffffb3"; public final static String COLOR_NO_CELL_LEFT = "#ffff4d"; public final static String COLOR_NO_ROW_RIGHT = "#ffecb3"; public final static String COLOR_NO_CELL_RIGHT = "#ffcc33"; public final static String COLOR_DIFFERENT = "#f0b3ff"; + public final static String COLOR_DIFFERENT_LESS = "#f8e6ff"; public final static String COLOR_ISSUE = "#ffad99"; protected ComparisonSession session; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructuralMatch.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructuralMatch.java index 26933c697..13930d1ce 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructuralMatch.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructuralMatch.java @@ -3,6 +3,7 @@ package org.hl7.fhir.r5.comparison; import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; @@ -87,5 +88,21 @@ public class StructuralMatch { return false; } + public void countMessages(MessageCounts cnts) { + for (ValidationMessage vm : getMessages()) { + if (vm.getLevel() == IssueSeverity.ERROR) { + cnts.error(); + } else if (vm.getLevel() == IssueSeverity.WARNING) { + cnts.warning(); + } else if (vm.getLevel() == IssueSeverity.INFORMATION) { + cnts.hint(); + } + } + for (StructuralMatch c : children) { + c.countMessages(cnts); + } + + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java index f8e48b143..1bf21ebbc 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java @@ -7,6 +7,8 @@ import java.util.List; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; +import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.Element; import org.hl7.fhir.r5.model.ValueSet; @@ -66,7 +68,26 @@ public class ValueSetComparer extends CanonicalResourceComparer { @Override protected String summary() { return "ValueSet: "+left.present()+" vs "+right.present(); + } + + @Override + protected String fhirType() { + return "ValueSet"; } + @Override + protected void countMessages(MessageCounts cnts) { + super.countMessages(cnts); + if (includes != null) { + includes.countMessages(cnts); + } + if (excludes != null) { + excludes.countMessages(cnts); + } + if (expansion != null) { + expansion.countMessages(cnts); + } + } + } public ValueSetComparer(ComparisonSession session) { @@ -372,16 +393,16 @@ public class ValueSetComparer extends CanonicalResourceComparer { } private void compareExpansions(ValueSet left, ValueSet right, ValueSetComparison res) { - ValueSet expL = left.hasExpansion() ? left : expand(left, res, "left"); - ValueSet expR = left.hasExpansion() ? left : expand(right, res, "right"); + ValueSet expL = left.hasExpansion() ? left : expand(left, res, "left", session.getContextLeft()); + ValueSet expR = left.hasExpansion() ? left : expand(right, res, "right", session.getContextRight()); if (expL != null && expR != null) { // ignore the parameters for now compareConcepts(expL.getExpansion().getContains(), expR.getExpansion().getContains(), res.forceExpansion(), res.getUnion().getExpansion().getContains(), res.getIntersection().getExpansion().getContains(), "ValueSet.expansion.contains", res); } } - private ValueSet expand(ValueSet vs, ValueSetComparison res, String name) { - ValueSetExpansionOutcome vse =session.getContext().expandVS(vs, true, false); + private ValueSet expand(ValueSet vs, ValueSetComparison res, String name, IWorkerContext ctxt) { + ValueSetExpansionOutcome vse = ctxt.expandVS(vs, true, false); if (vse.getValueset() != null) { return vse.getValueset(); } else { @@ -682,6 +703,11 @@ public class ValueSetComparer extends CanonicalResourceComparer { p.tx("Unable to generate expansion - see errors"); return p; } + if (csc.getExpansion().getChildren().isEmpty()) { + XhtmlNode p = new XhtmlNode(NodeType.Element, "p"); + p.tx("Expansion is empty"); + return p; + } // columns: code(+system), version, display , abstract, inactive, boolean hasSystem = csc.getExpansion().getChildren().isEmpty() ? false : getSystemVaries(csc.getExpansion(), csc.getExpansion().getChildren().get(0).either().getSystem()); boolean hasVersion = findVersion(csc.getExpansion()); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java index 562a2d01a..d4467f175 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java @@ -116,7 +116,7 @@ public class ComparisonTests { CanonicalResource left = load("left"); CanonicalResource right = load("right"); - ComparisonSession session = new ComparisonSession(context, "Comparison Tests", null); + ComparisonSession session = new ComparisonSession(context, context, "Comparison Tests", null); if (left instanceof CodeSystem && right instanceof CodeSystem) { CodeSystemComparer cs = new CodeSystemComparer(session); @@ -146,7 +146,7 @@ public class ComparisonTests { ProfileUtilities utils = new ProfileUtilities(context, null, null); genSnapshot(utils, (StructureDefinition) left); genSnapshot(utils, (StructureDefinition) right); - ProfileComparer pc = new ProfileComparer(session, utils); + ProfileComparer pc = new ProfileComparer(session, utils, utils); ProfileComparison csc = pc.compare((StructureDefinition) left, (StructureDefinition) right); new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "comparison", name + "-union.json")), csc.getUnion()); new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "comparison", name + "-intersection.json")), csc.getIntersection());