significant work on tests to support version comparison

This commit is contained in:
Grahame Grieve 2020-07-25 08:38:45 +10:00
parent 115fa8a497
commit 50dbc9bda1
9 changed files with 436 additions and 105 deletions

View File

@ -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 = "<td>"+left.getUrl()+"</td><td>"+right.getUrl()+"</td>";
} else if (left != null) {
s = "<td colspan=2>"+left.getUrl()+"</td>";
} else {
s = "<td colspan=2>"+right.getUrl()+"</td>";
}
s = s + "<td><a href=\""+getId()+".html\">Comparison</a></td>";
s = s + "<td>"+outcomeSummary()+"</td>";
return "<tr style=\"background-color: "+(hasErrors() ? COLOR_DIFFERENT : COLOR_DIFFERENT_LESS)+"\">"+s+"</tr>\r\n";
}
protected boolean hasErrors() {
MessageCounts cnts = new MessageCounts();
countMessages(cnts);
return cnts.getErrors() > 0;
}
@Override
protected void countMessages(MessageCounts cnts) {
for (StructuralMatch<String> sm : metadata.values()) {
sm.countMessages(cnts);
}
}
}
public CanonicalResourceComparer(ComparisonSession session) {

View File

@ -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;

View File

@ -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<String, String> 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("<li><a href=\""+comp.getId()+".html\">"+Utilities.escapeXml(comp.summary())+"</a></li>\r\n");
}
b.append("<table class=\"grid\">\r\n");
List<String> list = sorted(session.getCompares().keySet());
processList(list, b, "CodeSystem");
processList(list, b, "ValueSet");
processList(list, b, "StructureDefinition");
b.append("</table>\r\n");
Map<String, Base> 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<String> 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("<tr><td colspan=4><b>"+name+"</b></td></tr>\r\n");
}
renderComparison(id, comp);
b.append(comp.toTable());
//"<li><a href=\""+comp.getId()+".html\">"+Utilities.escapeXml(comp.summary())+"</a></li>\r\n"
}
}
}
private List<String> sorted(Set<String> keySet) {
List<String> 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 = "<html><body><pre>"+cnt+"</pre></body></html>\r\n";
TextFile.stringToFile(cnt, file(comp.getId()+".html"));
}
private void renderCodeSystem(String id, CodeSystemComparison comp) throws IOException {
String template = templates.get("CodeSystem");
Map<String, Base> 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<String, Base> 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<String, Base> 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);
}

View File

@ -25,24 +25,30 @@ import org.hl7.fhir.utilities.Utilities;
public class ComparisonSession {
private Map<String, ResourceComparison> 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;
}
}

View File

@ -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<ElementDefinition> sm = new StructuralMatch<ElementDefinition>(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<? extends TypeRefComponent> unionTypes(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError {
List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
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<ElementDefinition> res, String path, List<TypeRefComponent> results, TypeRefComponent nw) throws DefinitionException, IOException, FHIRFormatError {
private void checkAddTypeUnion(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, List<TypeRefComponent> 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<ElementDefinitionConstraintComponent> intersectConstraints(String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
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<ElementDefinition> res, String path, String url, String name) {
StructureDefinition sd = session.getContext().fetchResource(StructureDefinition.class, url);
private StructureDefinition resolveProfile(ProfileComparison comp, StructuralMatch<ElementDefinition> 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);
}

View File

@ -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 = "<td>"+left.getUrl()+"</td><td>"+right.getUrl()+"</td>";
} else if (left != null) {
s = "<td colspan=2>"+left.getUrl()+"</td>";
} else {
s = "<td colspan=2>"+right.getUrl()+"</td>";
}
if (left == null) {
s = s + "<td>Added</td>";
color = COLOR_NO_ROW_LEFT;
} else if (right == null) {
s = s + "<td>Removed</td>";
color = COLOR_NO_ROW_RIGHT;
} else {
s = s + "<td><a href=\""+getId()+".html\">Failed<a></td>";
color = COLOR_ISSUE;
}
s = s + "<td>"+(e != null ? Utilities.escapeXml(e.getMessage()) : "")+"</td>";
return "<tr style=\"background-color: "+color+"\">"+s+"</tr>\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;

View File

@ -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<T> {
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<T> c : children) {
c.countMessages(cnts);
}
}
}

View File

@ -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());

View File

@ -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());