Start building conformance resource comparison code
This commit is contained in:
parent
48d9c21748
commit
eb9de04d2d
|
@ -0,0 +1,190 @@
|
|||
package org.hl7.fhir.r5.comparison;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.PrimitiveType;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
|
||||
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;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
|
||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Title;
|
||||
|
||||
public abstract class CanonicalResourceComparer extends ResourceComparer {
|
||||
|
||||
|
||||
public class CanonicalResourceComparison<T extends CanonicalResource> extends ResourceCmparison {
|
||||
|
||||
protected T left;
|
||||
protected T right;
|
||||
protected T union;
|
||||
protected T intersection;
|
||||
protected Map<String, StructuralMatch<String>> metadata = new HashMap<>();
|
||||
|
||||
|
||||
public CanonicalResourceComparison(T left, T right) {
|
||||
super();
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
|
||||
public T getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public T getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public T getUnion() {
|
||||
return union;
|
||||
}
|
||||
|
||||
|
||||
public T getIntersection() {
|
||||
return intersection;
|
||||
}
|
||||
|
||||
|
||||
public Map<String, StructuralMatch<String>> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setLeft(T left) {
|
||||
this.left = left;
|
||||
}
|
||||
|
||||
|
||||
public void setRight(T right) {
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
|
||||
public void setUnion(T union) {
|
||||
this.union = union;
|
||||
}
|
||||
|
||||
|
||||
public void setIntersection(T intersection) {
|
||||
this.intersection = intersection;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public CanonicalResourceComparer(IWorkerContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
protected void compareMetadata(CanonicalResource left, CanonicalResource right, Map<String, StructuralMatch<String>> comp, CanonicalResourceComparison<? extends CanonicalResource> res) {
|
||||
comparePrimitives("url", left.getUrlElement(), right.getUrlElement(), comp, IssueSeverity.INFORMATION, res);
|
||||
comparePrimitives("version", left.getVersionElement(), right.getVersionElement(), comp, IssueSeverity.INFORMATION, res);
|
||||
comparePrimitives("name", left.getNameElement(), right.getNameElement(), comp, IssueSeverity.INFORMATION, res);
|
||||
comparePrimitives("title", left.getTitleElement(), right.getTitleElement(), comp, IssueSeverity.INFORMATION, res);
|
||||
comparePrimitives("status", left.getStatusElement(), right.getStatusElement(), comp, IssueSeverity.INFORMATION, res);
|
||||
comparePrimitives("experimental", left.getExperimentalElement(), right.getExperimentalElement(), comp, IssueSeverity.WARNING, res);
|
||||
comparePrimitives("date", left.getDateElement(), right.getDateElement(), comp, IssueSeverity.INFORMATION, res);
|
||||
comparePrimitives("publisher", left.getPublisherElement(), right.getPublisherElement(), comp, IssueSeverity.INFORMATION, res);
|
||||
comparePrimitives("description", left.getDescriptionElement(), right.getDescriptionElement(), comp, IssueSeverity.NULL, res);
|
||||
comparePrimitives("purpose", left.getPurposeElement(), right.getPurposeElement(), comp, IssueSeverity.NULL, res);
|
||||
comparePrimitives("copyright", left.getCopyrightElement(), right.getCopyrightElement(), comp, IssueSeverity.INFORMATION, res);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected void comparePrimitives(String name, PrimitiveType l, PrimitiveType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res) {
|
||||
StructuralMatch<String> match = null;
|
||||
if (l.isEmpty() && r.isEmpty()) {
|
||||
match = new StructuralMatch<>(null, null, null);
|
||||
} else if (l.isEmpty()) {
|
||||
match = new StructuralMatch<>(null, r.primitiveValue(), vm(IssueSeverity.INFORMATION, "Added this item", fhirType()+"."+name));
|
||||
} else if (r.isEmpty()) {
|
||||
match = new StructuralMatch<>(l.primitiveValue(), null, vm(IssueSeverity.INFORMATION, "Removed this item", fhirType()+"."+name));
|
||||
} else if (!l.hasValue() && !r.hasValue()) {
|
||||
match = new StructuralMatch<>(null, null, vm(IssueSeverity.INFORMATION, "No Value", fhirType()+"."+name));
|
||||
} else if (!l.hasValue()) {
|
||||
match = new StructuralMatch<>(null, r.primitiveValue(), vm(IssueSeverity.INFORMATION, "No Value on Left", fhirType()+"."+name));
|
||||
} else if (!r.hasValue()) {
|
||||
match = new StructuralMatch<>(l.primitiveValue(), null, vm(IssueSeverity.INFORMATION, "No Value on Right", fhirType()+"."+name));
|
||||
} else if (l.getValue().equals(r.getValue())) {
|
||||
match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), null);
|
||||
} else {
|
||||
match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), vm(level, "Values Differ", fhirType()+"."+name));
|
||||
if (level != IssueSeverity.NULL) {
|
||||
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.primitiveValue()+"' vs '"+r.primitiveValue()+"'", level));
|
||||
}
|
||||
}
|
||||
comp.put(name, match);
|
||||
}
|
||||
|
||||
protected abstract String fhirType();
|
||||
|
||||
public XhtmlNode renderMetadata(CanonicalResourceComparison<? extends CanonicalResource> comparison, String id, String prefix) throws FHIRException, IOException {
|
||||
// columns: code, display (left|right), properties (left|right)
|
||||
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false);
|
||||
TableModel model = gen.new TableModel(id, true);
|
||||
model.setAlternating(true);
|
||||
model.getTitles().add(gen.new Title(null, null, "Name", "Property Name", null, 100));
|
||||
model.getTitles().add(gen.new Title(null, null, "Value", "The value of the property", null, 200, 2));
|
||||
model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
|
||||
|
||||
for (String n : sorted(comparison.getMetadata().keySet())) {
|
||||
StructuralMatch<String> t = comparison.getMetadata().get(n);
|
||||
addRow(gen, model.getRows(), n, t);
|
||||
}
|
||||
return gen.generate(model, prefix, 0, null);
|
||||
}
|
||||
|
||||
private void addRow(HierarchicalTableGenerator gen, List<Row> rows, String name, StructuralMatch<String> t) {
|
||||
Row r = gen.new Row();
|
||||
rows.add(r);
|
||||
r.getCells().add(gen.new Cell(null, null, name, null, null));
|
||||
if (t.hasLeft() && t.hasRight()) {
|
||||
if (t.getLeft().equals(t.getRight())) {
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft(), null, null).span(2));
|
||||
} else {
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
r.getCells().add(gen.new Cell(null, null, t.getRight(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
}
|
||||
} else if (t.hasLeft()) {
|
||||
r.setColor(COLOR_NO_ROW_RIGHT);
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft(), null, null));
|
||||
r.getCells().add(missingCell(gen));
|
||||
} else if (t.hasRight()) {
|
||||
r.setColor(COLOR_NO_ROW_LEFT);
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(gen.new Cell(null, null, t.getRight(), null, null));
|
||||
} else {
|
||||
r.getCells().add(missingCell(gen).span(2));
|
||||
}
|
||||
r.getCells().add(cellForMessages(gen, t.getMessages()));
|
||||
}
|
||||
|
||||
|
||||
private List<String> sorted(Set<String> keys) {
|
||||
List<String> res = new ArrayList<>();
|
||||
res.addAll(keys);
|
||||
Collections.sort(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,423 @@
|
|||
package org.hl7.fhir.r5.comparison;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
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;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Title;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlDocument;
|
||||
|
||||
public class CodeSystemComparer extends CanonicalResourceComparer {
|
||||
|
||||
|
||||
public class CodeSystemComparison extends CanonicalResourceComparison<CodeSystem> {
|
||||
|
||||
private StructuralMatch<ConceptDefinitionComponent> combined;
|
||||
private Map<String, String> propMap = new HashMap<>(); // right to left; left retains it's name
|
||||
|
||||
public CodeSystemComparison(CodeSystem left, CodeSystem right) {
|
||||
super(left, right);
|
||||
combined = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(); // base
|
||||
}
|
||||
|
||||
public Map<String, String> getPropMap() {
|
||||
return propMap;
|
||||
}
|
||||
|
||||
public StructuralMatch<ConceptDefinitionComponent> getCombined() {
|
||||
return combined;
|
||||
}
|
||||
}
|
||||
|
||||
private CodeSystem right;
|
||||
|
||||
public CodeSystemComparer(IWorkerContext context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public CodeSystemComparison compare(CodeSystem left, CodeSystem right) {
|
||||
if (left == null)
|
||||
throw new DefinitionException("No CodeSystem provided (left)");
|
||||
if (right == null)
|
||||
throw new DefinitionException("No CodeSystem provided (right)");
|
||||
|
||||
|
||||
CodeSystemComparison res = new CodeSystemComparison(left, right);
|
||||
CodeSystem cs = new CodeSystem();
|
||||
res.setUnion(cs);
|
||||
cs.setId(UUID.randomUUID().toString().toLowerCase());
|
||||
cs.setUrl("urn:uuid:"+cs.getId());
|
||||
cs.setName("Union"+left.getName()+"And"+right.getName());
|
||||
cs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
|
||||
cs.setStatus(left.getStatus());
|
||||
cs.setDate(new Date());
|
||||
for (PropertyComponent pL : left.getProperty()) {
|
||||
cs.addProperty(pL.copy());
|
||||
}
|
||||
for (PropertyComponent pR : left.getProperty()) {
|
||||
PropertyComponent pL = findProperty(left, pR);
|
||||
if (pL == null) {
|
||||
String code = getUniqued(pR.getCode(), cs.getProperty());
|
||||
cs.addProperty(pR.copy().setCode(code));
|
||||
} else {
|
||||
res.getPropMap().put(pR.getCode(), pL.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
CodeSystem cs1 = new CodeSystem();
|
||||
res.setIntersection(cs1);
|
||||
cs1.setId(UUID.randomUUID().toString().toLowerCase());
|
||||
cs1.setUrl("urn:uuid:"+cs1.getId());
|
||||
cs1.setName("Intersection"+left.getName()+"And"+right.getName());
|
||||
cs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
|
||||
cs1.setStatus(left.getStatus());
|
||||
cs1.setDate(new Date());
|
||||
cs1.getProperty().addAll(cs.getProperty());
|
||||
|
||||
compareMetadata(left, right, res.getMetadata(), res);
|
||||
comparePrimitives("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res);
|
||||
comparePrimitives("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res);
|
||||
comparePrimitives("compositional", left.getCompositionalElement(), right.getCompositionalElement(), res.getMetadata(), IssueSeverity.WARNING, res);
|
||||
comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res);
|
||||
comparePrimitives("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res);
|
||||
|
||||
compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept");
|
||||
return res;
|
||||
}
|
||||
|
||||
private String getUniqued(String code, List<PropertyComponent> list) {
|
||||
int i = 0;
|
||||
while (true) {
|
||||
boolean ok = true;
|
||||
String res = code+(i == 0 ? "" : i);
|
||||
for (PropertyComponent t : list) {
|
||||
if (res.equals(t.getCode())) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PropertyComponent findProperty(CodeSystem left, PropertyComponent p) {
|
||||
for (PropertyComponent t : left.getProperty()) {
|
||||
if (p.hasUri() && t.hasUri() && p.getUri().equals(t.getUri())) {
|
||||
return t;
|
||||
} else if (!p.hasUri() && !t.hasUri() && p.getCode().equals(t.getCode())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private void compareConcepts(List<ConceptDefinitionComponent> left, List<ConceptDefinitionComponent> right, StructuralMatch<ConceptDefinitionComponent> combined,
|
||||
List<ConceptDefinitionComponent> union, List<ConceptDefinitionComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path) {
|
||||
List<ConceptDefinitionComponent> matchR = new ArrayList<>();
|
||||
for (ConceptDefinitionComponent l : left) {
|
||||
ConceptDefinitionComponent r = findInList(right, l);
|
||||
if (r == null) {
|
||||
union.add(l);
|
||||
combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, vm(IssueSeverity.INFORMATION, "Removed this concept", path)));
|
||||
} else {
|
||||
matchR.add(r);
|
||||
ConceptDefinitionComponent cdM = merge(l, r, csU.getProperty(), res);
|
||||
ConceptDefinitionComponent cdI = intersect(l, r, res);
|
||||
union.add(cdM);
|
||||
intersection.add(cdI);
|
||||
StructuralMatch<ConceptDefinitionComponent> sm = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, r);
|
||||
compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res);
|
||||
combined.getChildren().add(sm);
|
||||
compareConcepts(l.getConcept(), r.getConcept(), sm, cdM.getConcept(), cdI.getConcept(), csU, csI, res, path+".where(code='"+l.getCode()+"').concept");
|
||||
}
|
||||
}
|
||||
for (ConceptDefinitionComponent r : right) {
|
||||
if (!matchR.contains(r)) {
|
||||
union.add(r);
|
||||
combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(vm(IssueSeverity.INFORMATION, "Added this concept", path), r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConceptDefinitionComponent findInList(List<ConceptDefinitionComponent> list, ConceptDefinitionComponent item) {
|
||||
for (ConceptDefinitionComponent t : list) {
|
||||
if (t.getCode().equals(item.getCode())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void compare(List<ValidationMessage> msgs, ConceptDefinitionComponent l, ConceptDefinitionComponent r, String path, CodeSystemComparison res) {
|
||||
compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res);
|
||||
compareStrings(path, msgs, l.getDefinition(), r.getDefinition(), "definition", IssueSeverity.INFORMATION, res);
|
||||
}
|
||||
|
||||
private void compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CodeSystemComparison res) {
|
||||
if (!Utilities.noString(right)) {
|
||||
if (Utilities.noString(left)) {
|
||||
msgs.add(vm(level, "Value for "+name+" added", path));
|
||||
} else if (!left.equals(right)) {
|
||||
if (level != IssueSeverity.NULL) {
|
||||
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
|
||||
}
|
||||
msgs.add(vm(level, name+" changed from left to right", path));
|
||||
}
|
||||
} else if (!Utilities.noString(left)) {
|
||||
msgs.add(vm(level, "Value for "+name+" removed", path));
|
||||
}
|
||||
}
|
||||
|
||||
private ConceptDefinitionComponent merge(ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
|
||||
ConceptDefinitionComponent cd = l.copy();
|
||||
if (!l.hasDisplay() && r.hasDisplay()) {
|
||||
cd.setDisplay(r.getDisplay());
|
||||
}
|
||||
if (!l.hasDefinition() && r.hasDefinition()) {
|
||||
cd.setDefinition(r.getDefinition());
|
||||
}
|
||||
mergeProps(cd, l, r, destProps, res);
|
||||
mergeDesignations(cd, l, r);
|
||||
return cd;
|
||||
}
|
||||
|
||||
private ConceptDefinitionComponent intersect(ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
|
||||
ConceptDefinitionComponent cd = l.copy();
|
||||
if (l.hasDisplay() && !r.hasDisplay()) {
|
||||
cd.setDisplay(null);
|
||||
}
|
||||
if (l.hasDefinition() && !r.hasDefinition()) {
|
||||
cd.setDefinition(null);
|
||||
}
|
||||
intersectProps(cd, l, r, res);
|
||||
// mergeDesignations(cd, l, r);
|
||||
return cd;
|
||||
}
|
||||
|
||||
private void mergeDesignations(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r) {
|
||||
for (ConceptDefinitionDesignationComponent td : l.getDesignation()) {
|
||||
if (hasDesignation(td, r.getDesignation())) {
|
||||
cd.getDesignation().add(td);
|
||||
}
|
||||
}
|
||||
for (ConceptDefinitionDesignationComponent td : r.getDesignation()) {
|
||||
if (hasDesignation(td, l.getDesignation())) {
|
||||
cd.getDesignation().add(td);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean hasDesignation(ConceptDefinitionDesignationComponent td, List<ConceptDefinitionDesignationComponent> designation) {
|
||||
for (ConceptDefinitionDesignationComponent t : designation) {
|
||||
if (designationsMatch(td, t)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean designationsMatch(ConceptDefinitionDesignationComponent l, ConceptDefinitionDesignationComponent r) {
|
||||
if (l.hasUse() != r.hasUse()) {
|
||||
return false;
|
||||
}
|
||||
if (l.hasLanguage() != r.hasLanguage()) {
|
||||
return false;
|
||||
}
|
||||
if (l.hasValue() != r.hasValue()) {
|
||||
return false;
|
||||
}
|
||||
if (l.hasUse()) {
|
||||
if (l.getUse().equalsDeep(r.getUse())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (l.hasLanguage()) {
|
||||
if (l.getLanguageElement().equalsDeep(r.getLanguageElement())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (l.hasValue()) {
|
||||
if (l.getValueElement().equalsDeep(r.getValueElement())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void mergeProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
|
||||
List<ConceptPropertyComponent> matchR = new ArrayList<>();
|
||||
for (ConceptPropertyComponent lp : l.getProperty()) {
|
||||
ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
|
||||
if (rp == null) {
|
||||
cd.getProperty().add(lp);
|
||||
} else {
|
||||
matchR.add(rp);
|
||||
cd.getProperty().add(lp);
|
||||
if (lp.getValue().equalsDeep(rp.getValue())) {
|
||||
cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ConceptPropertyComponent rp : r.getProperty()) {
|
||||
if (!matchR.contains(rp)) {
|
||||
cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void intersectProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
|
||||
for (ConceptPropertyComponent lp : l.getProperty()) {
|
||||
ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
|
||||
if (rp != null) {
|
||||
cd.getProperty().add(lp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConceptPropertyComponent findRightProp(List<ConceptPropertyComponent> rightProperties, ConceptPropertyComponent lp, CodeSystemComparison res) {
|
||||
for (ConceptPropertyComponent p : rightProperties) {
|
||||
if (res.getPropMap().get(p.getCode()).equals(lp.getCode())) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public XhtmlNode renderConcepts(CodeSystemComparison comparison, String id, String prefix) throws FHIRException, IOException {
|
||||
// columns: code, display (left|right), properties (left|right)
|
||||
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false);
|
||||
TableModel model = gen.new TableModel(id, true);
|
||||
model.setAlternating(true);
|
||||
model.getTitles().add(gen.new Title(null, null, "Code", "The code for the concept", null, 100));
|
||||
model.getTitles().add(gen.new Title(null, null, "Display", "The display for the concept", null, 200, 2));
|
||||
for (PropertyComponent p : comparison.getUnion().getProperty()) {
|
||||
model.getTitles().add(gen.new Title(null, null, p.getCode(), p.getDescription(), null, 100, 2));
|
||||
}
|
||||
model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
|
||||
for (StructuralMatch<ConceptDefinitionComponent> t : comparison.getCombined().getChildren()) {
|
||||
addRow(gen, model.getRows(), t, comparison);
|
||||
}
|
||||
return gen.generate(model, prefix, 0, null);
|
||||
}
|
||||
|
||||
private void addRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ConceptDefinitionComponent> t, CodeSystemComparison comparison) {
|
||||
Row r = gen.new Row();
|
||||
rows.add(r);
|
||||
r.getCells().add(gen.new Cell(null, null, t.either().getCode(), null, null));
|
||||
if (t.hasLeft() && t.hasRight()) {
|
||||
if (t.getLeft().hasDisplay() && t.getRight().hasDisplay()) {
|
||||
if (t.getLeft().getDisplay().equals(t.getRight().getDisplay())) {
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));
|
||||
} else {
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
}
|
||||
} else if (t.getLeft().hasDisplay()) {
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null));
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
|
||||
} else if (t.getRight().hasDisplay()) {
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
|
||||
r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null));
|
||||
} else {
|
||||
r.getCells().add(missingCell(gen).span(2));
|
||||
}
|
||||
for (PropertyComponent p : comparison.getUnion().getProperty()) {
|
||||
ConceptPropertyComponent lp = getProp(t.getLeft(), p, false, comparison);
|
||||
ConceptPropertyComponent rp = getProp(t.getRight(), p, true, comparison);
|
||||
|
||||
if (lp != null && rp != null) {
|
||||
if (lp.getValue().equals(rp.getValue())) {
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));
|
||||
} else {
|
||||
r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));
|
||||
r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));
|
||||
}
|
||||
} else if (lp != null) {
|
||||
r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
|
||||
} else if (rp != null) {
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
|
||||
r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));
|
||||
} else {
|
||||
r.getCells().add(missingCell(gen).span(2));
|
||||
}
|
||||
|
||||
}
|
||||
} else if (t.hasLeft()) {
|
||||
r.setColor(COLOR_NO_ROW_RIGHT);
|
||||
r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
|
||||
r.getCells().add(missingCell(gen));
|
||||
for (PropertyComponent p : comparison.getUnion().getProperty()) {
|
||||
r.getCells().add(propertyCell(gen, t.getLeft(), p, false, comparison));
|
||||
r.getCells().add(missingCell(gen));
|
||||
}
|
||||
} else {
|
||||
r.setColor(COLOR_NO_ROW_LEFT);
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
|
||||
for (PropertyComponent p : comparison.getUnion().getProperty()) {
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(propertyCell(gen, t.getLeft(), p, true, comparison));
|
||||
}
|
||||
}
|
||||
r.getCells().add(cellForMessages(gen, t.getMessages()));
|
||||
}
|
||||
|
||||
private Cell propertyCell(HierarchicalTableGenerator gen, ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
|
||||
ConceptPropertyComponent cp = getProp(cd, p, right, comp);
|
||||
if (cp == null) {
|
||||
return missingCell(gen, right ? COLOR_NO_CELL_RIGHT : COLOR_NO_CELL_LEFT);
|
||||
} else {
|
||||
return gen.new Cell(null, null, cp.getValue().toString(), null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public ConceptPropertyComponent getProp(ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
|
||||
String c = p.getCode();
|
||||
if (right) {
|
||||
c = comp.getPropMap().get(c);
|
||||
}
|
||||
ConceptPropertyComponent cp = null;
|
||||
for (ConceptPropertyComponent t : cd.getProperty()) {
|
||||
if (t.getCode().equals(c)) {
|
||||
cp = t;
|
||||
}
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String fhirType() {
|
||||
return "CodeSystem";
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,814 @@
|
|||
package org.hl7.fhir.r5.comparison;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.CanonicalResourceComparison;
|
||||
import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison;
|
||||
import org.hl7.fhir.r5.comparison.OldProfileComparer.ProfileComparison;
|
||||
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
|
||||
import org.hl7.fhir.r5.conformance.ProfileUtilities;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.DataType;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition;
|
||||
import org.hl7.fhir.r5.model.IntegerType;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
|
||||
import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
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.Source;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.r5.utils.DefinitionNavigator;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
|
||||
public class ProfileComparer extends CanonicalResourceComparer {
|
||||
|
||||
private static final int BOTH_NULL = 0;
|
||||
private static final int EITHER_NULL = 1;
|
||||
|
||||
public class ProfileComparison extends CanonicalResourceComparison<StructureDefinition> {
|
||||
|
||||
private StructuralMatch<ElementDefinition> combined;
|
||||
|
||||
public ProfileComparison(StructureDefinition left, StructureDefinition right) {
|
||||
super(left, right);
|
||||
combined = new StructuralMatch<ElementDefinition>(); // base
|
||||
}
|
||||
|
||||
public StructuralMatch<ElementDefinition> getCombined() {
|
||||
return combined;
|
||||
}
|
||||
}
|
||||
|
||||
public ProfileComparer(IWorkerContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String fhirType() {
|
||||
return "StructureDefinition";
|
||||
}
|
||||
|
||||
public ProfileComparison compare(StructureDefinition left, StructureDefinition right) throws DefinitionException, FHIRFormatError, IOException {
|
||||
check(left, "left");
|
||||
check(right, "right");
|
||||
|
||||
ProfileComparison res = new ProfileComparison(left, right);
|
||||
StructureDefinition sd = new StructureDefinition();
|
||||
res.setUnion(sd);
|
||||
sd.setId(UUID.randomUUID().toString().toLowerCase());
|
||||
sd.setUrl("urn:uuid:"+sd.getId());
|
||||
sd.setName("Union"+left.getName()+"And"+right.getName());
|
||||
sd.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
|
||||
sd.setStatus(left.getStatus());
|
||||
sd.setDate(new Date());
|
||||
|
||||
StructureDefinition sd1 = new StructureDefinition();
|
||||
res.setIntersection(sd1);
|
||||
sd1.setId(UUID.randomUUID().toString().toLowerCase());
|
||||
sd1.setUrl("urn:uuid:"+sd1.getId());
|
||||
sd1.setName("Intersection"+left.getName()+"And"+right.getName());
|
||||
sd1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
|
||||
sd1.setStatus(left.getStatus());
|
||||
sd1.setDate(new Date());
|
||||
|
||||
compareMetadata(left, right, res.getMetadata(), res);
|
||||
comparePrimitives("fhirVersion", left.getFhirVersionElement(), right.getFhirVersionElement(), res.getMetadata(), IssueSeverity.WARNING, res);
|
||||
comparePrimitives("kind", left.getKindElement(), right.getKindElement(), res.getMetadata(), IssueSeverity.WARNING, res);
|
||||
comparePrimitives("abstract", left.getAbstractElement(), right.getAbstractElement(), res.getMetadata(), IssueSeverity.WARNING, res);
|
||||
comparePrimitives("type", left.getTypeElement(), right.getTypeElement(), res.getMetadata(), IssueSeverity.ERROR, res);
|
||||
comparePrimitives("baseDefinition", left.getBaseDefinitionElement(), right.getBaseDefinitionElement(), res.getMetadata(), IssueSeverity.ERROR, res);
|
||||
|
||||
if (left.getType().equals(right.getType())) {
|
||||
DefinitionNavigator ln = new DefinitionNavigator(context, left);
|
||||
DefinitionNavigator rn = new DefinitionNavigator(context, right);
|
||||
// StructuralMatch<ElementDefinition> res = new StructuralMatch<ElementDefinition>(left.current(), right.current());
|
||||
// compareElements(res, res.getCombined(), ln.path(), null, ln, rn, sd, sd1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
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.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
|
||||
throw new DefinitionException("StructureDefinition is not for an profile - can't be compared ("+name+": "+sd.getName()+")");
|
||||
}
|
||||
if (sd.getSnapshot().getElement().isEmpty())
|
||||
throw new DefinitionException("StructureDefinition snapshot is empty ("+name+": "+sd.getName()+")");
|
||||
}
|
||||
|
||||
private void compareElements(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, String sliceName, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, FHIRFormatError, IOException {
|
||||
assert(path != null);
|
||||
assert(left != null);
|
||||
assert(right != null);
|
||||
assert(left.path().equals(right.path()));
|
||||
|
||||
// not allowed to be different:
|
||||
ruleCompares(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), path+".defaultValue[x]", BOTH_NULL);
|
||||
ruleEqual(comp, res, path, left.current().getMeaningWhenMissing(), right.current().getMeaningWhenMissing(), "meaningWhenMissing Must be the same", true);
|
||||
ruleEqual(comp, res, left.current().getIsModifier(), right.current().getIsModifier(), path, "isModifier");
|
||||
ruleEqual(comp, res, left.current().getIsSummary(), right.current().getIsSummary(), path, "isSummary");
|
||||
|
||||
// we ignore slicing right now - we're going to clone the root one anyway, and then think about clones
|
||||
// simple stuff
|
||||
ElementDefinition subset = new ElementDefinition();
|
||||
subset.setPath(left.path());
|
||||
if (sliceName != null)
|
||||
subset.setSliceName(sliceName);
|
||||
|
||||
|
||||
subset.getRepresentation().addAll(left.current().getRepresentation()); // can't be bothered even testing this one
|
||||
subset.setDefaultValue(left.current().getDefaultValue());
|
||||
subset.setMeaningWhenMissing(left.current().getMeaningWhenMissing());
|
||||
subset.setIsModifier(left.current().getIsModifier());
|
||||
subset.setIsSummary(left.current().getIsSummary());
|
||||
|
||||
// descriptive properties from ElementDefinition - merge them:
|
||||
subset.setLabel(mergeText(comp, res, path, "label", left.current().getLabel(), right.current().getLabel()));
|
||||
subset.setShort(mergeText(comp, res, path, "short", left.current().getShort(), right.current().getShort()));
|
||||
subset.setDefinition(mergeText(comp, res, path, "definition", left.current().getDefinition(), right.current().getDefinition()));
|
||||
subset.setComment(mergeText(comp, res, path, "comments", left.current().getComment(), right.current().getComment()));
|
||||
subset.setRequirements(mergeText(comp, res, path, "requirements", left.current().getRequirements(), right.current().getRequirements()));
|
||||
subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode()));
|
||||
subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias()));
|
||||
subset.getMapping().addAll(mergeMappings(left.current().getMapping(), right.current().getMapping()));
|
||||
// left will win for example
|
||||
subset.setExample(left.current().hasExample() ? left.current().getExample() : right.current().getExample());
|
||||
|
||||
subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport());
|
||||
ElementDefinition superset = subset.copy();
|
||||
|
||||
|
||||
// compare and intersect
|
||||
superset.setMin(unionMin(left.current().getMin(), right.current().getMin()));
|
||||
superset.setMax(unionMax(left.current().getMax(), right.current().getMax()));
|
||||
subset.setMin(intersectMin(left.current().getMin(), right.current().getMin()));
|
||||
subset.setMax(intersectMax(left.current().getMax(), right.current().getMax()));
|
||||
rule(comp, res, subset.getMax().equals("*") || Integer.parseInt(subset.getMax()) >= subset.getMin(), path, "Cardinality Mismatch: "+card(left)+"/"+card(right));
|
||||
|
||||
superset.getType().addAll(unionTypes(path, left.current().getType(), right.current().getType()));
|
||||
subset.getType().addAll(intersectTypes(comp, res, subset, path, left.current().getType(), right.current().getType()));
|
||||
rule(comp, res, !subset.getType().isEmpty() || (!left.current().hasType() && !right.current().hasType()), path, "Type Mismatch:\r\n "+typeCode(left)+"\r\n "+typeCode(right));
|
||||
// <fixed[x]><!-- ?? 0..1 * Value must be exactly this --></fixed[x]>
|
||||
// <pattern[x]><!-- ?? 0..1 * Value must have at least these property values --></pattern[x]>
|
||||
superset.setMaxLengthElement(unionMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
|
||||
subset.setMaxLengthElement(intersectMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
|
||||
if (left.current().hasBinding() || right.current().hasBinding()) {
|
||||
compareBindings(comp, res, subset, superset, path, left.current(), right.current());
|
||||
}
|
||||
// note these are backwards
|
||||
superset.getConstraint().addAll(intersectConstraints(path, left.current().getConstraint(), right.current().getConstraint()));
|
||||
subset.getConstraint().addAll(unionConstraints(comp, res, path, left.current().getConstraint(), right.current().getConstraint()));
|
||||
comp.getIntersection().getSnapshot().getElement().add(subset);
|
||||
comp.getUnion().getSnapshot().getElement().add(superset);
|
||||
|
||||
// add the children
|
||||
compareChildren(comp, res, path, left, right);
|
||||
//
|
||||
// // now process the slices
|
||||
// if (left.current().hasSlicing() || right.current().hasSlicing()) {
|
||||
// assert sliceName == null;
|
||||
// if (isExtension(left.path()))
|
||||
// return compareExtensions(outcome, path, superset, subset, left, right);
|
||||
// // return true;
|
||||
// else {
|
||||
// ElementDefinitionSlicingComponent slicingL = left.current().getSlicing();
|
||||
// ElementDefinitionSlicingComponent slicingR = right.current().getSlicing();
|
||||
// // well, this is tricky. If one is sliced, and the other is not, then in general, the union just ignores the slices, and the intersection is the slices.
|
||||
// if (left.current().hasSlicing() && !right.current().hasSlicing()) {
|
||||
// // the super set is done. Any restrictions in the slices are irrelevant to what the super set says, except that we're going sum up the value sets if we can (for documentation purposes) (todo)
|
||||
// // the minimum set is the slicing specified in the slicer
|
||||
// subset.setSlicing(slicingL);
|
||||
// // stick everything from the right to do with the slices to the subset
|
||||
// copySlices(outcome.subset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), left.slices());
|
||||
// } else if (!left.current().hasSlicing() && right.current().hasSlicing()) {
|
||||
// // the super set is done. Any restrictions in the slices are irrelevant to what the super set says, except that we're going sum up the value sets if we can (for documentation purposes) (todo)
|
||||
// // the minimum set is the slicing specified in the slicer
|
||||
// subset.setSlicing(slicingR);
|
||||
// // stick everything from the right to do with the slices to the subset
|
||||
// copySlices(outcome.subset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), right.slices());
|
||||
// } else if (isTypeSlicing(slicingL) || isTypeSlicing(slicingR)) {
|
||||
// superset.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
|
||||
// subset.getSlicing().setRules(slicingL.getRules() == SlicingRules.CLOSED || slicingR.getRules() == SlicingRules.CLOSED ? SlicingRules.OPEN : SlicingRules.CLOSED).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
|
||||
//
|
||||
// // the superset is the union of the types
|
||||
// // the subset is the intersection of them
|
||||
// List<DefinitionNavigator> handled = new ArrayList<>();
|
||||
// for (DefinitionNavigator t : left.slices()) {
|
||||
// DefinitionNavigator r = findMatchingSlice(right.slices(), t);
|
||||
// if (r == null) {
|
||||
// copySlice(outcome.superset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), t);
|
||||
// } else {
|
||||
// handled.add(r);
|
||||
// ret = compareElements(outcome, path+":"+t.current().getSliceName(), t, r, t.current().getSliceName()) && ret;
|
||||
// }
|
||||
// }
|
||||
// for (DefinitionNavigator t : right.slices()) {
|
||||
// if (!handled.contains(t)) {
|
||||
// copySlice(outcome.superset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), t);
|
||||
// }
|
||||
// }
|
||||
// } else if (slicingMatches(slicingL, slicingR)) {
|
||||
// // if it's the same, we can try matching the slices - though we might have to give up without getting matches correct
|
||||
// // there amy be implied consistency we can't reason about
|
||||
// throw new DefinitionException("Slicing matches but is not handled yet at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+")");
|
||||
// } else {
|
||||
// // if the slicing is different, we can't compare them - or can we?
|
||||
// throw new DefinitionException("Slicing doesn't match at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+" / "+ProfileUtilities.summarizeSlicing(slicingR)+")");
|
||||
// }
|
||||
// }
|
||||
// // todo: name
|
||||
// }
|
||||
// return ret;
|
||||
//
|
||||
// // TODO Auto-generated method stub
|
||||
// return null;
|
||||
}
|
||||
|
||||
private void compareChildren(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError {
|
||||
List<DefinitionNavigator> lc = left.children();
|
||||
List<DefinitionNavigator> rc = right.children();
|
||||
// it's possible that one of these profiles walks into a data type and the other doesn't
|
||||
// if it does, we have to load the children for that data into the profile that doesn't
|
||||
// walk into it
|
||||
if (lc.isEmpty() && !rc.isEmpty() && right.current().getType().size() == 1 && left.hasTypeChildren(right.current().getType().get(0)))
|
||||
lc = left.childrenFromType(right.current().getType().get(0));
|
||||
if (rc.isEmpty() && !lc.isEmpty() && left.current().getType().size() == 1 && right.hasTypeChildren(left.current().getType().get(0)))
|
||||
rc = right.childrenFromType(left.current().getType().get(0));
|
||||
|
||||
List<DefinitionNavigator> matchR = new ArrayList<>();
|
||||
for (DefinitionNavigator l : lc) {
|
||||
DefinitionNavigator r = findInList(rc, l);
|
||||
if (r == null) {
|
||||
comp.getUnion().getSnapshot().getElement().add(l.current().copy());
|
||||
res.getChildren().add(new StructuralMatch<ElementDefinition>(l.current(), vm(IssueSeverity.INFORMATION, "Removed this element", path)));
|
||||
} else {
|
||||
matchR.add(r);
|
||||
StructuralMatch<ElementDefinition> sm = new StructuralMatch<ElementDefinition>(l.current(), r.current());
|
||||
res.getChildren().add(sm);
|
||||
compareElements(comp, sm, l.path(), null, left, right);
|
||||
}
|
||||
}
|
||||
for (DefinitionNavigator r : rc) {
|
||||
if (!matchR.contains(r)) {
|
||||
comp.getUnion().getSnapshot().getElement().add(r.current().copy());
|
||||
res.getChildren().add(new StructuralMatch<ElementDefinition>(vm(IssueSeverity.INFORMATION, "Added this element", path), r.current()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DefinitionNavigator findInList(List<DefinitionNavigator> rc, DefinitionNavigator l) {
|
||||
// TODO: fix
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean ruleCompares(ProfileComparison comp, StructuralMatch<ElementDefinition> res, DataType vLeft, DataType vRight, String path, int nullStatus) throws IOException {
|
||||
// TODO: fix
|
||||
// if (vLeft == null && vRight == null && nullStatus == BOTH_NULL)
|
||||
// return true;
|
||||
// if (vLeft == null && vRight == null) {
|
||||
// res.getMessages().add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Must be the same and not null (null/null)", ValidationMessage.IssueSeverity.ERROR));
|
||||
// status(ed, ProfileUtilities.STATUS_ERROR);
|
||||
// }
|
||||
// if (vLeft == null && nullStatus == EITHER_NULL)
|
||||
// return true;
|
||||
// if (vRight == null && nullStatus == EITHER_NULL)
|
||||
// return true;
|
||||
// if (vLeft == null || vRight == null || !Base.compareDeep(vLeft, vRight, false)) {
|
||||
// res.getMessages().add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Must be the same ("+toString(vLeft)+"/"+toString(vRight)+")", ValidationMessage.IssueSeverity.ERROR));
|
||||
// status(ed, ProfileUtilities.STATUS_ERROR);
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean rule(ProfileComparison comp, StructuralMatch<ElementDefinition> res, boolean test, String path, String message) {
|
||||
// TODO: fix
|
||||
// if (!test) {
|
||||
// messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, message, ValidationMessage.IssueSeverity.ERROR));
|
||||
// status(ed, ProfileUtilities.STATUS_ERROR);
|
||||
// }
|
||||
return test;
|
||||
}
|
||||
|
||||
|
||||
private boolean ruleEqual(ProfileComparison comp, StructuralMatch<ElementDefinition> res, boolean vLeft, boolean vRight, String path, String elementName) {
|
||||
// TODO: fix
|
||||
// if (vLeft != vRight) {
|
||||
// res.getMessages().add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, elementName+" must be the same ("+vLeft+"/"+vRight+")", ValidationMessage.IssueSeverity.ERROR));
|
||||
// status(ed, ProfileUtilities.STATUS_ERROR);
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean ruleEqual(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, String vLeft, String vRight, String description, boolean nullOK) {
|
||||
// TODO: fix
|
||||
// if (vLeft == null && vRight == null && nullOK)
|
||||
// return true;
|
||||
// if (vLeft == null && vRight == null) {
|
||||
// res.getMessages().add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, description+" and not null (null/null)", ValidationMessage.IssueSeverity.ERROR));
|
||||
// if (ed != null)
|
||||
// status(ed, ProfileUtilities.STATUS_ERROR);
|
||||
// }
|
||||
// if (vLeft == null || !vLeft.equals(vRight)) {
|
||||
// res.getMessages().add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, description+" ("+vLeft+"/"+vRight+")", ValidationMessage.IssueSeverity.ERROR));
|
||||
// if (ed != null)
|
||||
// status(ed, ProfileUtilities.STATUS_ERROR);
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
private String mergeText(ProfileComparison outcome, StructuralMatch<ElementDefinition> sm, String path, String name, String left, String right) {
|
||||
// TODO: fix
|
||||
// if (left == null && right == null)
|
||||
// return null;
|
||||
// if (left == null)
|
||||
// return right;
|
||||
// if (right == null)
|
||||
// return left;
|
||||
// left = stripLinks(left);
|
||||
// right = stripLinks(right);
|
||||
// if (left.equalsIgnoreCase(right))
|
||||
// return left;
|
||||
// if (path != null) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.INFORMATIONAL, path, "Elements differ in definition for "+name+":\r\n \""+left+"\"\r\n \""+right+"\"",
|
||||
// "Elements differ in definition for "+name+":<br/>\""+Utilities.escapeXml(left)+"\"<br/>\""+Utilities.escapeXml(right)+"\"", ValidationMessage.IssueSeverity.INFORMATION));
|
||||
// status(ed, ProfileUtilities.STATUS_HINT);
|
||||
// }
|
||||
return "left: "+left+"; right: "+right;
|
||||
}
|
||||
|
||||
|
||||
private List<Coding> mergeCodings(List<Coding> left, List<Coding> right) {
|
||||
List<Coding> result = new ArrayList<Coding>();
|
||||
result.addAll(left);
|
||||
for (Coding c : right) {
|
||||
boolean found = false;
|
||||
for (Coding ct : left)
|
||||
if (Utilities.equals(c.getSystem(), ct.getSystem()) && Utilities.equals(c.getCode(), ct.getCode()))
|
||||
found = true;
|
||||
if (!found)
|
||||
result.add(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private List<StringType> mergeStrings(List<StringType> left, List<StringType> right) {
|
||||
List<StringType> result = new ArrayList<StringType>();
|
||||
result.addAll(left);
|
||||
for (StringType c : right) {
|
||||
boolean found = false;
|
||||
for (StringType ct : left)
|
||||
if (Utilities.equals(c.getValue(), ct.getValue()))
|
||||
found = true;
|
||||
if (!found)
|
||||
result.add(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<ElementDefinitionMappingComponent> mergeMappings(List<ElementDefinitionMappingComponent> left, List<ElementDefinitionMappingComponent> right) {
|
||||
List<ElementDefinitionMappingComponent> result = new ArrayList<ElementDefinitionMappingComponent>();
|
||||
result.addAll(left);
|
||||
for (ElementDefinitionMappingComponent c : right) {
|
||||
boolean found = false;
|
||||
for (ElementDefinitionMappingComponent ct : left)
|
||||
if (Utilities.equals(c.getIdentity(), ct.getIdentity()) && Utilities.equals(c.getLanguage(), ct.getLanguage()) && Utilities.equals(c.getMap(), ct.getMap()))
|
||||
found = true;
|
||||
if (!found)
|
||||
result.add(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int intersectMin(int left, int right) {
|
||||
if (left > right)
|
||||
return left;
|
||||
else
|
||||
return right;
|
||||
}
|
||||
|
||||
private int unionMin(int left, int right) {
|
||||
if (left > right)
|
||||
return right;
|
||||
else
|
||||
return left;
|
||||
}
|
||||
|
||||
private String intersectMax(String left, String right) {
|
||||
int l = "*".equals(left) ? Integer.MAX_VALUE : Integer.parseInt(left);
|
||||
int r = "*".equals(right) ? Integer.MAX_VALUE : Integer.parseInt(right);
|
||||
if (l < r)
|
||||
return left;
|
||||
else
|
||||
return right;
|
||||
}
|
||||
|
||||
private String unionMax(String left, String right) {
|
||||
int l = "*".equals(left) ? Integer.MAX_VALUE : Integer.parseInt(left);
|
||||
int r = "*".equals(right) ? Integer.MAX_VALUE : Integer.parseInt(right);
|
||||
if (l < r)
|
||||
return right;
|
||||
else
|
||||
return left;
|
||||
}
|
||||
|
||||
private IntegerType intersectMaxLength(int left, int right) {
|
||||
if (left == 0)
|
||||
left = Integer.MAX_VALUE;
|
||||
if (right == 0)
|
||||
right = Integer.MAX_VALUE;
|
||||
if (left < right)
|
||||
return left == Integer.MAX_VALUE ? null : new IntegerType(left);
|
||||
else
|
||||
return right == Integer.MAX_VALUE ? null : new IntegerType(right);
|
||||
}
|
||||
|
||||
private IntegerType unionMaxLength(int left, int right) {
|
||||
if (left == 0)
|
||||
left = Integer.MAX_VALUE;
|
||||
if (right == 0)
|
||||
right = Integer.MAX_VALUE;
|
||||
if (left < right)
|
||||
return right == Integer.MAX_VALUE ? null : new IntegerType(right);
|
||||
else
|
||||
return left == Integer.MAX_VALUE ? null : new IntegerType(left);
|
||||
}
|
||||
|
||||
private String card(DefinitionNavigator defn) {
|
||||
return Integer.toString(defn.current().getMin())+".."+defn.current().getMax();
|
||||
}
|
||||
|
||||
|
||||
private Collection<? extends TypeRefComponent> unionTypes(String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError {
|
||||
List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
|
||||
for (TypeRefComponent l : left)
|
||||
checkAddTypeUnion(path, result, l);
|
||||
for (TypeRefComponent r : right)
|
||||
checkAddTypeUnion(path, result, r);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void checkAddTypeUnion(String path, List<TypeRefComponent> results, TypeRefComponent nw) throws DefinitionException, IOException, FHIRFormatError {
|
||||
// TODO: fix
|
||||
// boolean pfound = false;
|
||||
// boolean tfound = false;
|
||||
// nw = nw.copy();
|
||||
// if (nw.hasAggregation())
|
||||
// throw new DefinitionException("Aggregation not supported: "+path);
|
||||
// for (TypeRefComponent ex : results) {
|
||||
// if (Utilities.equals(ex.getWorkingCode(), nw.getWorkingCode())) {
|
||||
// if (!ex.hasProfile() && !nw.hasProfile())
|
||||
// pfound = true;
|
||||
// else if (!ex.hasProfile()) {
|
||||
// pfound = true;
|
||||
// } else if (!nw.hasProfile()) {
|
||||
// pfound = true;
|
||||
// ex.setProfile(null);
|
||||
// } else {
|
||||
// // both have profiles. Is one derived from the other?
|
||||
// StructureDefinition sdex = context.fetchResource(StructureDefinition.class, ex.getProfile().get(0).getValue());
|
||||
// StructureDefinition sdnw = context.fetchResource(StructureDefinition.class, nw.getProfile().get(0).getValue());
|
||||
// if (sdex != null && sdnw != null) {
|
||||
// if (sdex == sdnw) {
|
||||
// pfound = true;
|
||||
// } else if (derivesFrom(sdex, sdnw)) {
|
||||
// ex.setProfile(nw.getProfile());
|
||||
// pfound = true;
|
||||
// } else if (derivesFrom(sdnw, sdex)) {
|
||||
// pfound = true;
|
||||
// } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
|
||||
// ProfileComparison comp = compareProfiles(sdex, sdnw);
|
||||
// if (comp.getSuperset() != null) {
|
||||
// pfound = true;
|
||||
// ex.addProfile("#"+comp.id);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (!ex.hasTargetProfile() && !nw.hasTargetProfile())
|
||||
// tfound = true;
|
||||
// else if (!ex.hasTargetProfile()) {
|
||||
// tfound = true;
|
||||
// } else if (!nw.hasTargetProfile()) {
|
||||
// tfound = true;
|
||||
// ex.setTargetProfile(null);
|
||||
// } else {
|
||||
// // both have profiles. Is one derived from the other?
|
||||
// StructureDefinition sdex = context.fetchResource(StructureDefinition.class, ex.getTargetProfile().get(0).getValue());
|
||||
// StructureDefinition sdnw = context.fetchResource(StructureDefinition.class, nw.getTargetProfile().get(0).getValue());
|
||||
// if (sdex != null && sdnw != null) {
|
||||
// if (sdex == sdnw) {
|
||||
// tfound = true;
|
||||
// } else if (derivesFrom(sdex, sdnw)) {
|
||||
// ex.setTargetProfile(nw.getTargetProfile());
|
||||
// tfound = true;
|
||||
// } else if (derivesFrom(sdnw, sdex)) {
|
||||
// tfound = true;
|
||||
// } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
|
||||
// ProfileComparison comp = compareProfiles(sdex, sdnw);
|
||||
// if (comp.getSuperset() != null) {
|
||||
// tfound = true;
|
||||
// ex.addTargetProfile("#"+comp.id);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (!tfound || !pfound)
|
||||
// results.add(nw);
|
||||
}
|
||||
|
||||
|
||||
private boolean derivesFrom(StructureDefinition left, StructureDefinition right) {
|
||||
// left derives from right if it's base is the same as right
|
||||
// todo: recursive...
|
||||
return left.hasBaseDefinition() && left.getBaseDefinition().equals(right.getUrl());
|
||||
}
|
||||
|
||||
|
||||
private Collection<? extends TypeRefComponent> intersectTypes(ProfileComparison comp, StructuralMatch<ElementDefinition> res, ElementDefinition ed, String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError {
|
||||
// TODO: fix
|
||||
List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
|
||||
// for (TypeRefComponent l : left) {
|
||||
// if (l.hasAggregation())
|
||||
// throw new DefinitionException("Aggregation not supported: "+path);
|
||||
// boolean pfound = false;
|
||||
// boolean tfound = false;
|
||||
// TypeRefComponent c = l.copy();
|
||||
// for (TypeRefComponent r : right) {
|
||||
// if (r.hasAggregation())
|
||||
// throw new DefinitionException("Aggregation not supported: "+path);
|
||||
// if (!l.hasProfile() && !r.hasProfile()) {
|
||||
// pfound = true;
|
||||
// } else if (!r.hasProfile()) {
|
||||
// pfound = true;
|
||||
// } else if (!l.hasProfile()) {
|
||||
// pfound = true;
|
||||
// c.setProfile(r.getProfile());
|
||||
// } else {
|
||||
// StructureDefinition sdl = resolveProfile(ed, outcome, path, l.getProfile().get(0).getValue(), outcome.leftName());
|
||||
// StructureDefinition sdr = resolveProfile(ed, outcome, path, r.getProfile().get(0).getValue(), outcome.rightName());
|
||||
// if (sdl != null && sdr != null) {
|
||||
// if (sdl == sdr) {
|
||||
// pfound = true;
|
||||
// } else if (derivesFrom(sdl, sdr)) {
|
||||
// pfound = true;
|
||||
// } else if (derivesFrom(sdr, sdl)) {
|
||||
// c.setProfile(r.getProfile());
|
||||
// pfound = true;
|
||||
// } else if (sdl.getType().equals(sdr.getType())) {
|
||||
// ProfileComparison comp = compareProfiles(sdl, sdr);
|
||||
// if (comp.getSubset() != null) {
|
||||
// pfound = true;
|
||||
// c.addProfile("#"+comp.id);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (!l.hasTargetProfile() && !r.hasTargetProfile()) {
|
||||
// tfound = true;
|
||||
// } else if (!r.hasTargetProfile()) {
|
||||
// tfound = true;
|
||||
// } else if (!l.hasTargetProfile()) {
|
||||
// tfound = true;
|
||||
// c.setTargetProfile(r.getTargetProfile());
|
||||
// } else {
|
||||
// StructureDefinition sdl = resolveProfile(ed, outcome, path, l.getTargetProfile().get(0).getValue(), outcome.leftName());
|
||||
// StructureDefinition sdr = resolveProfile(ed, outcome, path, r.getTargetProfile().get(0).getValue(), outcome.rightName());
|
||||
// if (sdl != null && sdr != null) {
|
||||
// if (sdl == sdr) {
|
||||
// tfound = true;
|
||||
// } else if (derivesFrom(sdl, sdr)) {
|
||||
// tfound = true;
|
||||
// } else if (derivesFrom(sdr, sdl)) {
|
||||
// c.setTargetProfile(r.getTargetProfile());
|
||||
// tfound = true;
|
||||
// } else if (sdl.getType().equals(sdr.getType())) {
|
||||
// ProfileComparison comp = compareProfiles(sdl, sdr);
|
||||
// if (comp.getSubset() != null) {
|
||||
// tfound = true;
|
||||
// c.addTargetProfile("#"+comp.id);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (pfound && tfound)
|
||||
// result.add(c);
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
|
||||
private String typeCode(DefinitionNavigator defn) {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (TypeRefComponent t : defn.current().getType())
|
||||
b.append(t.getWorkingCode()+(t.hasProfile() ? "("+t.getProfile()+")" : "")+(t.hasTargetProfile() ? "("+t.getTargetProfile()+")" : "")); // todo: other properties
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
|
||||
private boolean compareBindings(ProfileComparison comp, StructuralMatch<ElementDefinition> res, ElementDefinition subset, ElementDefinition superset, String path, ElementDefinition lDef, ElementDefinition rDef) throws FHIRFormatError {
|
||||
// TODO: fix
|
||||
// assert(lDef.hasBinding() || rDef.hasBinding());
|
||||
// if (!lDef.hasBinding()) {
|
||||
// subset.setBinding(rDef.getBinding());
|
||||
// // technically, the super set is unbound, but that's not very useful - so we use the provided on as an example
|
||||
// superset.setBinding(rDef.getBinding().copy());
|
||||
// superset.getBinding().setStrength(BindingStrength.EXAMPLE);
|
||||
// return true;
|
||||
// }
|
||||
// if (!rDef.hasBinding()) {
|
||||
// subset.setBinding(lDef.getBinding());
|
||||
// superset.setBinding(lDef.getBinding().copy());
|
||||
// superset.getBinding().setStrength(BindingStrength.EXAMPLE);
|
||||
// return true;
|
||||
// }
|
||||
// ElementDefinitionBindingComponent left = lDef.getBinding();
|
||||
// ElementDefinitionBindingComponent right = rDef.getBinding();
|
||||
// if (Base.compareDeep(left, right, false)) {
|
||||
// subset.setBinding(left);
|
||||
// superset.setBinding(right);
|
||||
// }
|
||||
//
|
||||
// // if they're both examples/preferred then:
|
||||
// // subset: left wins if they're both the same
|
||||
// // superset:
|
||||
// if (isPreferredOrExample(left) && isPreferredOrExample(right)) {
|
||||
// if (right.getStrength() == BindingStrength.PREFERRED && left.getStrength() == BindingStrength.EXAMPLE && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Example/preferred bindings differ at "+path+" using binding from "+outcome.rightName(), ValidationMessage.IssueSeverity.INFORMATION));
|
||||
// status(subset, ProfileUtilities.STATUS_HINT);
|
||||
// subset.setBinding(right);
|
||||
// superset.setBinding(unionBindings(superset, outcome, path, left, right));
|
||||
// } else {
|
||||
// if ((right.getStrength() != BindingStrength.EXAMPLE || left.getStrength() != BindingStrength.EXAMPLE) && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false) ) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Example/preferred bindings differ at "+path+" using binding from "+outcome.leftName(), ValidationMessage.IssueSeverity.INFORMATION));
|
||||
// status(subset, ProfileUtilities.STATUS_HINT);
|
||||
// }
|
||||
// subset.setBinding(left);
|
||||
// superset.setBinding(unionBindings(superset, outcome, path, left, right));
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
// // if either of them are extensible/required, then it wins
|
||||
// if (isPreferredOrExample(left)) {
|
||||
// subset.setBinding(right);
|
||||
// superset.setBinding(unionBindings(superset, outcome, path, left, right));
|
||||
// return true;
|
||||
// }
|
||||
// if (isPreferredOrExample(right)) {
|
||||
// subset.setBinding(left);
|
||||
// superset.setBinding(unionBindings(superset, outcome, path, left, right));
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// // ok, both are extensible or required.
|
||||
// ElementDefinitionBindingComponent subBinding = new ElementDefinitionBindingComponent();
|
||||
// subset.setBinding(subBinding);
|
||||
// ElementDefinitionBindingComponent superBinding = new ElementDefinitionBindingComponent();
|
||||
// superset.setBinding(superBinding);
|
||||
// subBinding.setDescription(mergeText(subset, outcome, path, "description", left.getDescription(), right.getDescription()));
|
||||
// superBinding.setDescription(mergeText(subset, outcome, null, "description", left.getDescription(), right.getDescription()));
|
||||
// if (left.getStrength() == BindingStrength.REQUIRED || right.getStrength() == BindingStrength.REQUIRED)
|
||||
// subBinding.setStrength(BindingStrength.REQUIRED);
|
||||
// else
|
||||
// subBinding.setStrength(BindingStrength.EXTENSIBLE);
|
||||
// if (left.getStrength() == BindingStrength.EXTENSIBLE || right.getStrength() == BindingStrength.EXTENSIBLE)
|
||||
// superBinding.setStrength(BindingStrength.EXTENSIBLE);
|
||||
// else
|
||||
// superBinding.setStrength(BindingStrength.REQUIRED);
|
||||
//
|
||||
// if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) {
|
||||
// subBinding.setValueSet(left.getValueSet());
|
||||
// superBinding.setValueSet(left.getValueSet());
|
||||
// return true;
|
||||
// } else if (!left.hasValueSet()) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "No left Value set at "+path, ValidationMessage.IssueSeverity.ERROR));
|
||||
// return true;
|
||||
// } else if (!right.hasValueSet()) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "No right Value set at "+path, ValidationMessage.IssueSeverity.ERROR));
|
||||
// return true;
|
||||
// } else {
|
||||
// // ok, now we compare the value sets. This may be unresolvable.
|
||||
// ValueSet lvs = resolveVS(outcome.left, left.getValueSet());
|
||||
// ValueSet rvs = resolveVS(outcome.right, right.getValueSet());
|
||||
// if (lvs == null) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Unable to resolve left value set "+left.getValueSet().toString()+" at "+path, ValidationMessage.IssueSeverity.ERROR));
|
||||
// return true;
|
||||
// } else if (rvs == null) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Unable to resolve right value set "+right.getValueSet().toString()+" at "+path, ValidationMessage.IssueSeverity.ERROR));
|
||||
// return true;
|
||||
// } else {
|
||||
// // first, we'll try to do it by definition
|
||||
// ValueSet cvs = intersectByDefinition(lvs, rvs);
|
||||
// if(cvs == null) {
|
||||
// // if that didn't work, we'll do it by expansion
|
||||
// ValueSetExpansionOutcome le;
|
||||
// ValueSetExpansionOutcome re;
|
||||
// try {
|
||||
// le = context.expandVS(lvs, true, false);
|
||||
// re = context.expandVS(rvs, true, false);
|
||||
// if (le.getError() != null) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value set "+lvs.getUrl()+" could not be expanded", ValidationMessage.IssueSeverity.ERROR));
|
||||
// } else if (re.getError() != null) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value set "+rvs.getUrl()+" could not be expanded", ValidationMessage.IssueSeverity.ERROR));
|
||||
// } else if (!closed(le.getValueset())) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value set "+lvs.getUrl()+" is not closed, so can't be compased", ValidationMessage.IssueSeverity.ERROR));
|
||||
// } else if (!closed(re.getValueset())) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value set "+rvs.getUrl()+" is not closed, so can't be compased", ValidationMessage.IssueSeverity.ERROR));
|
||||
// } else {
|
||||
// cvs = intersectByExpansion(path, le.getValueset(), re.getValueset());
|
||||
// if (!cvs.getCompose().hasInclude()) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value sets "+lvs.getUrl()+" and "+rvs.getUrl()+" do not intersect", ValidationMessage.IssueSeverity.ERROR));
|
||||
// status(subset, ProfileUtilities.STATUS_ERROR);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e){
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Unable to expand or process value sets "+lvs.getUrl()+" and "+rvs.getUrl()+": "+e.getMessage(), ValidationMessage.IssueSeverity.ERROR));
|
||||
// status(subset, ProfileUtilities.STATUS_ERROR);
|
||||
// e.printStackTrace();
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// if (cvs != null) {
|
||||
// subBinding.setValueSet("#"+addValueSet(cvs));
|
||||
// superBinding.setValueSet("#"+addValueSet(unite(superset, outcome, path, lvs, rvs)));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ElementDefinitionConstraintComponent> intersectConstraints(String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
|
||||
List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
|
||||
for (ElementDefinitionConstraintComponent l : left) {
|
||||
boolean found = false;
|
||||
for (ElementDefinitionConstraintComponent r : right)
|
||||
if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity()))
|
||||
found = true;
|
||||
if (found)
|
||||
result.add(l);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// we can't really know about constraints. We create warnings, and collate them
|
||||
private List<ElementDefinitionConstraintComponent> unionConstraints(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
|
||||
// TODO: fix
|
||||
List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
|
||||
// for (ElementDefinitionConstraintComponent l : left) {
|
||||
// boolean found = false;
|
||||
// for (ElementDefinitionConstraintComponent r : right)
|
||||
// if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity()))
|
||||
// found = true;
|
||||
// if (!found) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "StructureDefinition "+outcome.leftName()+" has a constraint that is not found in "+outcome.rightName()+" and it is uncertain whether they are compatible ("+l.getXpath()+")", ValidationMessage.IssueSeverity.INFORMATION));
|
||||
// status(ed, ProfileUtilities.STATUS_WARNING);
|
||||
// }
|
||||
// result.add(l);
|
||||
// }
|
||||
// for (ElementDefinitionConstraintComponent r : right) {
|
||||
// boolean found = false;
|
||||
// for (ElementDefinitionConstraintComponent l : left)
|
||||
// if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity()))
|
||||
// found = true;
|
||||
// if (!found) {
|
||||
// outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "StructureDefinition "+outcome.rightName()+" has a constraint that is not found in "+outcome.leftName()+" and it is uncertain whether they are compatible ("+r.getXpath()+")", ValidationMessage.IssueSeverity.INFORMATION));
|
||||
// status(ed, ProfileUtilities.STATUS_WARNING);
|
||||
// result.add(r);
|
||||
// }
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package org.hl7.fhir.r5.comparison;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
|
||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlDocument;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
|
||||
|
||||
public class ResourceComparer {
|
||||
|
||||
public class ResourceCmparison {
|
||||
protected List<ValidationMessage> messages = new ArrayList<>();
|
||||
|
||||
public List<ValidationMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
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_ISSUE = "#ffad99";
|
||||
|
||||
protected IWorkerContext context;
|
||||
|
||||
public ResourceComparer(IWorkerContext context) {
|
||||
super();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public Cell missingCell(HierarchicalTableGenerator gen) {
|
||||
Cell c = gen.new Cell(null, null, "", null, null);
|
||||
return c;
|
||||
}
|
||||
|
||||
public Cell missingCell(HierarchicalTableGenerator gen, String color) {
|
||||
Cell c = gen.new Cell(null, null, "", null, null);
|
||||
if (color != null) {
|
||||
c.setStyle("background-color: "+color);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
public XhtmlNode renderErrors(ResourceCmparison csc) {
|
||||
XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
|
||||
XhtmlNode tbl = div.table("grid");
|
||||
for (ValidationMessage vm : csc.messages) {
|
||||
XhtmlNode tr = tbl.tr();
|
||||
tr.td().tx(vm.getLocation());
|
||||
tr.td().tx(vm.getMessage());
|
||||
tr.td().tx(vm.getLevel().getDisplay());
|
||||
}
|
||||
return div;
|
||||
}
|
||||
|
||||
|
||||
protected ValidationMessage vm(IssueSeverity level, String message, String path) {
|
||||
return new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, message, level == IssueSeverity.NULL ? IssueSeverity.INFORMATION : level);
|
||||
}
|
||||
|
||||
private String colorForLevel(IssueSeverity level) {
|
||||
switch (level) {
|
||||
case ERROR:
|
||||
return "#ffcccc";
|
||||
case FATAL:
|
||||
return "#ff9999";
|
||||
case WARNING:
|
||||
return "#ffebcc";
|
||||
default: // INFORMATION:
|
||||
return "#ffffe6";
|
||||
}
|
||||
}
|
||||
|
||||
private String halfColorForLevel(IssueSeverity level) {
|
||||
switch (level) {
|
||||
case ERROR:
|
||||
return "#ffeeee";
|
||||
case FATAL:
|
||||
return "#ffcccc";
|
||||
case WARNING:
|
||||
return "#fff4ee";
|
||||
default: // INFORMATION:
|
||||
return "#fffff2";
|
||||
}
|
||||
}
|
||||
|
||||
protected Cell cellForMessages(HierarchicalTableGenerator gen, List<ValidationMessage> messages) {
|
||||
Cell cell = gen.new Cell();
|
||||
Piece piece = gen.new Piece("ul");
|
||||
cell.addPiece(piece);
|
||||
for (ValidationMessage msg : messages) {
|
||||
XhtmlNode li = new XhtmlNode(NodeType.Element, "li");
|
||||
piece.getChildren().add(li);
|
||||
li.style("background-color: "+halfColorForLevel(msg.getLevel()));
|
||||
li.tx(msg.getMessage());
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.hl7.fhir.r5.comparison;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
|
||||
public class StructuralMatch<T> {
|
||||
|
||||
private T left;
|
||||
private T right;
|
||||
private List<ValidationMessage> messages = new ArrayList<>();
|
||||
private List<StructuralMatch<T>> children = new ArrayList<>();
|
||||
|
||||
public StructuralMatch() {
|
||||
// root, just a place holder...
|
||||
}
|
||||
|
||||
public StructuralMatch(T left, T right) {
|
||||
super();
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public StructuralMatch(T left, T right, ValidationMessage msg) {
|
||||
super();
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
if (msg != null) {
|
||||
this.messages.add(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public StructuralMatch(ValidationMessage msg, T right) {
|
||||
super();
|
||||
this.messages.add(msg);
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public StructuralMatch(T left, ValidationMessage msg) {
|
||||
super();
|
||||
this.left = left;
|
||||
this.messages.add(msg);
|
||||
}
|
||||
|
||||
public T getLeft() {
|
||||
return left;
|
||||
}
|
||||
public T getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public List<StructuralMatch<T>> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* return left if it exists, or return right (which might be null)
|
||||
*
|
||||
* This is used when accessing whatever makes the items common
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public T either() {
|
||||
return left != null ? left : right;
|
||||
}
|
||||
|
||||
public boolean hasLeft() {
|
||||
return left != null;
|
||||
}
|
||||
|
||||
public boolean hasRight() {
|
||||
return right != null;
|
||||
}
|
||||
|
||||
public List<ValidationMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,820 @@
|
|||
package org.hl7.fhir.r5.comparison;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.model.BackboneElement;
|
||||
import org.hl7.fhir.r5.model.CanonicalType;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
|
||||
import org.hl7.fhir.r5.model.Element;
|
||||
import org.hl7.fhir.r5.model.UriType;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
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;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlDocument;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
|
||||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Title;
|
||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||
|
||||
public class ValueSetComparer extends CanonicalResourceComparer {
|
||||
|
||||
public class ValueSetComparison extends CanonicalResourceComparison<ValueSet> {
|
||||
|
||||
public ValueSetComparison(ValueSet left, ValueSet right) {
|
||||
super(left, right);
|
||||
}
|
||||
|
||||
private StructuralMatch<Element> includes = new StructuralMatch<>();
|
||||
private StructuralMatch<Element> excludes = new StructuralMatch<>();
|
||||
private StructuralMatch<ValueSetExpansionContainsComponent> expansion;
|
||||
|
||||
public StructuralMatch<Element> getIncludes() {
|
||||
return includes;
|
||||
}
|
||||
|
||||
public StructuralMatch<Element> getExcludes() {
|
||||
return excludes;
|
||||
}
|
||||
|
||||
public StructuralMatch<ValueSetExpansionContainsComponent> getExpansion() {
|
||||
return expansion;
|
||||
}
|
||||
|
||||
public StructuralMatch<ValueSetExpansionContainsComponent> forceExpansion() {
|
||||
if (expansion == null) {
|
||||
expansion = new StructuralMatch<>();
|
||||
}
|
||||
return expansion;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueSetComparer(IWorkerContext context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public ValueSetComparison compare(ValueSet left, ValueSet right) {
|
||||
if (left == null)
|
||||
throw new DefinitionException("No ValueSet provided (left)");
|
||||
if (right == null)
|
||||
throw new DefinitionException("No ValueSet provided (right)");
|
||||
|
||||
ValueSetComparison res = new ValueSetComparison(left, right);
|
||||
ValueSet vs = new ValueSet();
|
||||
res.setUnion(vs);
|
||||
vs.setId(UUID.randomUUID().toString().toLowerCase());
|
||||
vs.setUrl("urn:uuid:"+vs.getId());
|
||||
vs.setName("Union"+left.getName()+"And"+right.getName());
|
||||
vs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
|
||||
vs.setStatus(left.getStatus());
|
||||
vs.setDate(new Date());
|
||||
|
||||
ValueSet vs1 = new ValueSet();
|
||||
res.setIntersection(vs1);
|
||||
vs1.setId(UUID.randomUUID().toString().toLowerCase());
|
||||
vs1.setUrl("urn:uuid:"+vs1.getId());
|
||||
vs1.setName("Intersection"+left.getName()+"And"+right.getName());
|
||||
vs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
|
||||
vs1.setStatus(left.getStatus());
|
||||
vs1.setDate(new Date());
|
||||
|
||||
compareMetadata(left, right, res.getMetadata(), res);
|
||||
comparePrimitives("immutable", left.getImmutableElement(), right.getImmutableElement(), res.getMetadata(), IssueSeverity.WARNING, res);
|
||||
if (left.hasCompose() || right.hasCompose()) {
|
||||
comparePrimitives("compose.lockedDate", left.getCompose().getLockedDateElement(), right.getCompose().getLockedDateElement(), res.getMetadata(), IssueSeverity.WARNING, res);
|
||||
comparePrimitives("compose.inactive", left.getCompose().getInactiveElement(), right.getCompose().getInactiveElement(), res.getMetadata(), IssueSeverity.WARNING, res);
|
||||
}
|
||||
|
||||
compareCompose(left.getCompose(), right.getCompose(), res, res.getUnion().getCompose(), res.getIntersection().getCompose());
|
||||
compareExpansions(left, right, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void compareCompose(ValueSetComposeComponent left, ValueSetComposeComponent right, ValueSetComparison res, ValueSetComposeComponent union, ValueSetComposeComponent intersection) {
|
||||
// first, the includes
|
||||
List<ConceptSetComponent> matchR = new ArrayList<>();
|
||||
for (ConceptSetComponent l : left.getInclude()) {
|
||||
ConceptSetComponent r = findInList(right.getInclude(), l, left.getInclude());
|
||||
if (r == null) {
|
||||
union.getInclude().add(l);
|
||||
res.getIncludes().getChildren().add(new StructuralMatch<Element>(l, vm(IssueSeverity.INFORMATION, "Removed Include", "ValueSet.compose.include")));
|
||||
} else {
|
||||
matchR.add(r);
|
||||
ConceptSetComponent csM = new ConceptSetComponent();
|
||||
ConceptSetComponent csI = new ConceptSetComponent();
|
||||
union.getInclude().add(csM);
|
||||
intersection.getInclude().add(csI);
|
||||
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
|
||||
res.getIncludes().getChildren().add(sm);
|
||||
compareDefinitions(l, r, sm, csM, csI);
|
||||
}
|
||||
}
|
||||
for (ConceptSetComponent r : right.getInclude()) {
|
||||
if (!matchR.contains(r)) {
|
||||
union.getInclude().add(r);
|
||||
res.getIncludes().getChildren().add(new StructuralMatch<Element>(vm(IssueSeverity.INFORMATION, "Added Include", "ValueSet.compose.include"), r));
|
||||
}
|
||||
}
|
||||
|
||||
// now. the excludes
|
||||
matchR.clear();
|
||||
for (ConceptSetComponent l : left.getExclude()) {
|
||||
ConceptSetComponent r = findInList(right.getExclude(), l, left.getExclude());
|
||||
if (r == null) {
|
||||
union.getExclude().add(l);
|
||||
res.getExcludes().getChildren().add(new StructuralMatch<Element>(l, vm(IssueSeverity.INFORMATION, "Removed Exclude", "ValueSet.compose.exclude")));
|
||||
} else {
|
||||
matchR.add(r);
|
||||
ConceptSetComponent csM = new ConceptSetComponent();
|
||||
ConceptSetComponent csI = new ConceptSetComponent();
|
||||
union.getExclude().add(csM);
|
||||
intersection.getExclude().add(csI);
|
||||
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
|
||||
res.getExcludes().getChildren().add(sm);
|
||||
compareDefinitions(l, r, sm, csM, csI);
|
||||
}
|
||||
}
|
||||
for (ConceptSetComponent r : right.getExclude()) {
|
||||
if (!matchR.contains(r)) {
|
||||
union.getExclude().add(r);
|
||||
res.getExcludes().getChildren().add(new StructuralMatch<Element>(vm(IssueSeverity.INFORMATION, "Added Exclude", "ValueSet.compose.exclude"), r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConceptSetComponent findInList(List<ConceptSetComponent> matches, ConceptSetComponent item, List<ConceptSetComponent> source) {
|
||||
if (matches.size() == 1 && source.size() == 1) {
|
||||
return matches.get(0);
|
||||
}
|
||||
int matchCount = countMatchesBySystem(matches, item);
|
||||
int sourceCount = countMatchesBySystem(source, item);
|
||||
|
||||
if (matchCount == 1 && sourceCount == 1) {
|
||||
for (ConceptSetComponent t : matches) {
|
||||
if (t.getSystem().equals(item.getSystem())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if there's more than one candidate match by system, then we look for a full match
|
||||
for (ConceptSetComponent t : matches) {
|
||||
if (t.equalsDeep(item)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int countMatchesBySystem(List<ConceptSetComponent> list, ConceptSetComponent item) {
|
||||
int c = 0;
|
||||
for (ConceptSetComponent t : list) {
|
||||
if (t.getSystem().equals(item.getSystem())) {
|
||||
c++;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
private void compareDefinitions(ConceptSetComponent left, ConceptSetComponent right, StructuralMatch<Element> combined, ConceptSetComponent union, ConceptSetComponent intersection) {
|
||||
// system must match, but the rest might not. we're going to do the full comparison whatever, so the outcome looks consistent to the user
|
||||
List<CanonicalType> matchVSR = new ArrayList<>();
|
||||
for (CanonicalType l : left.getValueSet()) {
|
||||
CanonicalType r = findInList(right.getValueSet(), l, left.getValueSet());
|
||||
if (r == null) {
|
||||
union.getValueSet().add(l);
|
||||
combined.getChildren().add(new StructuralMatch<Element>(l, vm(IssueSeverity.INFORMATION, "Removed ValueSet", "ValueSet.compose.include.valueSet")));
|
||||
} else {
|
||||
matchVSR.add(r);
|
||||
if (l.getValue().equals(r.getValue())) {
|
||||
union.getValueSet().add(l);
|
||||
intersection.getValueSet().add(l);
|
||||
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, null);
|
||||
combined.getChildren().add(sm);
|
||||
} else {
|
||||
union.getValueSet().add(l);
|
||||
union.getValueSet().add(r);
|
||||
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vm(IssueSeverity.INFORMATION, "Values are different", "ValueSet.compose.include.valueSet"));
|
||||
combined.getChildren().add(sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (CanonicalType r : right.getValueSet()) {
|
||||
if (!matchVSR.contains(r)) {
|
||||
union.getValueSet().add(r);
|
||||
combined.getChildren().add(new StructuralMatch<Element>(vm(IssueSeverity.INFORMATION, "Add ValueSet", "ValueSet.compose.include.valueSet"), r));
|
||||
}
|
||||
}
|
||||
|
||||
List<ConceptReferenceComponent> matchCR = new ArrayList<>();
|
||||
for (ConceptReferenceComponent l : left.getConcept()) {
|
||||
ConceptReferenceComponent r = findInList(right.getConcept(), l, left.getConcept());
|
||||
if (r == null) {
|
||||
union.getConcept().add(l);
|
||||
combined.getChildren().add(new StructuralMatch<Element>(l, vm(IssueSeverity.INFORMATION, "Removed this Concept", "ValueSet.compose.include.concept")));
|
||||
} else {
|
||||
matchCR.add(r);
|
||||
if (l.getCode().equals(r.getCode())) {
|
||||
ConceptReferenceComponent cu = new ConceptReferenceComponent();
|
||||
ConceptReferenceComponent ci = new ConceptReferenceComponent();
|
||||
union.getConcept().add(cu);
|
||||
intersection.getConcept().add(ci);
|
||||
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
|
||||
combined.getChildren().add(sm);
|
||||
compareConcepts(l, r, sm, cu, ci);
|
||||
} else {
|
||||
union.getConcept().add(l);
|
||||
union.getConcept().add(r);
|
||||
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vm(IssueSeverity.INFORMATION, "Concepts are different", "ValueSet.compose.include.concept"));
|
||||
combined.getChildren().add(sm);
|
||||
compareConcepts(l, r, sm, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ConceptReferenceComponent r : right.getConcept()) {
|
||||
if (!matchCR.contains(r)) {
|
||||
union.getConcept().add(r);
|
||||
combined.getChildren().add(new StructuralMatch<Element>(vm(IssueSeverity.INFORMATION, "Added this Concept", "ValueSet.compose.include.concept"), r));
|
||||
}
|
||||
}
|
||||
|
||||
List<ConceptSetFilterComponent> matchFR = new ArrayList<>();
|
||||
for (ConceptSetFilterComponent l : left.getFilter()) {
|
||||
ConceptSetFilterComponent r = findInList(right.getFilter(), l, left.getFilter());
|
||||
if (r == null) {
|
||||
union.getFilter().add(l);
|
||||
combined.getChildren().add(new StructuralMatch<Element>(l, vm(IssueSeverity.INFORMATION, "Removed this item", "ValueSet.compose.include.filter")));
|
||||
} else {
|
||||
matchFR.add(r);
|
||||
if (l.getProperty().equals(r.getProperty()) && l.getOp().equals(r.getOp())) {
|
||||
ConceptSetFilterComponent cu = new ConceptSetFilterComponent();
|
||||
ConceptSetFilterComponent ci = new ConceptSetFilterComponent();
|
||||
union.getFilter().add(cu);
|
||||
intersection.getFilter().add(ci);
|
||||
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
|
||||
combined.getChildren().add(sm);
|
||||
compareFilters(l, r, sm, cu, ci);
|
||||
} else {
|
||||
union.getFilter().add(l);
|
||||
union.getFilter().add(r);
|
||||
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vm(IssueSeverity.INFORMATION, "Codes are different", "ValueSet.compose.include.filter"));
|
||||
combined.getChildren().add(sm);
|
||||
compareFilters(l, r, sm, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ConceptSetFilterComponent r : right.getFilter()) {
|
||||
if (!matchFR.contains(r)) {
|
||||
union.getFilter().add(r);
|
||||
combined.getChildren().add(new StructuralMatch<Element>(vm(IssueSeverity.INFORMATION, "Added this item", "ValueSet.compose.include.filter"), r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void compareConcepts(ConceptReferenceComponent l, ConceptReferenceComponent r, StructuralMatch<Element> sm, ConceptReferenceComponent cu, ConceptReferenceComponent ci) {
|
||||
sm.getChildren().add(new StructuralMatch<Element>(l.getCodeElement(), r.getCodeElement(), l.getCode().equals(r.getCode()) ? null : vm(IssueSeverity.INFORMATION, "Codes do not match", "ValueSet.compose.include.concept")));
|
||||
if (ci != null) {
|
||||
ci.setCode(l.getCode());
|
||||
cu.setCode(l.getCode());
|
||||
}
|
||||
if (l.hasDisplay() && r.hasDisplay()) {
|
||||
sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), r.getDisplayElement(), l.getDisplay().equals(r.getDisplay()) ? null : vm(IssueSeverity.INFORMATION, "Displays do not match", "ValueSet.compose.include.concept")));
|
||||
if (ci != null) {
|
||||
ci.setDisplay(r.getDisplay());
|
||||
cu.setDisplay(r.getDisplay());
|
||||
}
|
||||
} else if (l.hasDisplay()) {
|
||||
sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), null, vm(IssueSeverity.INFORMATION, "Display Removed", "ValueSet.compose.include.concept")));
|
||||
if (ci != null) {
|
||||
ci.setDisplay(l.getDisplay());
|
||||
cu.setDisplay(l.getDisplay());
|
||||
}
|
||||
} else if (r.hasDisplay()) {
|
||||
sm.getChildren().add(new StructuralMatch<Element>(null, r.getDisplayElement(), vm(IssueSeverity.INFORMATION, "Display added", "ValueSet.compose.include.concept")));
|
||||
if (ci != null) {
|
||||
ci.setDisplay(r.getDisplay());
|
||||
cu.setDisplay(r.getDisplay());
|
||||
}
|
||||
} else {
|
||||
sm.getChildren().add(new StructuralMatch<Element>(null, null, vm(IssueSeverity.INFORMATION, "No Display", "ValueSet.compose.include.concept")));
|
||||
}
|
||||
}
|
||||
|
||||
private void compareFilters(ConceptSetFilterComponent l, ConceptSetFilterComponent r, StructuralMatch<Element> sm, ConceptSetFilterComponent cu, ConceptSetFilterComponent ci) {
|
||||
sm.getChildren().add(new StructuralMatch<Element>(l.getPropertyElement(), r.getPropertyElement(), l.getProperty().equals(r.getProperty()) ? null : vm(IssueSeverity.INFORMATION, "Properties do not match", "ValueSet.compose.include.concept")));
|
||||
sm.getChildren().add(new StructuralMatch<Element>(l.getOpElement(), r.getOpElement(), l.getOp().equals(r.getOp()) ? null : vm(IssueSeverity.INFORMATION, "Filter Operations do not match", "ValueSet.compose.include.concept")));
|
||||
sm.getChildren().add(new StructuralMatch<Element>(l.getValueElement(), r.getValueElement(), l.getValue().equals(r.getValue()) ? null : vm(IssueSeverity.INFORMATION, "Values do not match", "ValueSet.compose.include.concept")));
|
||||
if (ci != null) {
|
||||
ci.setProperty(l.getProperty());
|
||||
ci.setOp(l.getOp());
|
||||
ci.setValue(l.getValue());
|
||||
cu.setProperty(l.getProperty());
|
||||
cu.setOp(l.getOp());
|
||||
cu.setValue(l.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private CanonicalType findInList(List<CanonicalType> matches, CanonicalType item, List<CanonicalType> source) {
|
||||
if (matches.size() == 1 && source.size() == 1) {
|
||||
return matches.get(0);
|
||||
}
|
||||
for (CanonicalType t : matches) {
|
||||
if (t.getValue().equals(item.getValue())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ConceptReferenceComponent findInList(List<ConceptReferenceComponent> matches, ConceptReferenceComponent item, List<ConceptReferenceComponent> source) {
|
||||
if (matches.size() == 1 && source.size() == 1) {
|
||||
return matches.get(0);
|
||||
}
|
||||
for (ConceptReferenceComponent t : matches) {
|
||||
if (t.getCode().equals(item.getCode())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ConceptSetFilterComponent findInList(List<ConceptSetFilterComponent> matches, ConceptSetFilterComponent item, List<ConceptSetFilterComponent> source) {
|
||||
if (matches.size() == 1 && source.size() == 1) {
|
||||
return matches.get(0);
|
||||
}
|
||||
for (ConceptSetFilterComponent t : matches) {
|
||||
if (t.getProperty().equals(item.getProperty()) && t.getOp().equals(item.getOp()) ) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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");
|
||||
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 = context.expandVS(vs, true, false);
|
||||
if (vse.getValueset() != null) {
|
||||
return vse.getValueset();
|
||||
} else {
|
||||
res.getMessages().add(new ValidationMessage(Source.TerminologyEngine, IssueType.EXCEPTION, "ValueSet", "Error Expanding "+name+":"+vse.getError(), IssueSeverity.ERROR));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void compareConcepts(List<ValueSetExpansionContainsComponent> left, List<ValueSetExpansionContainsComponent> right, StructuralMatch<ValueSetExpansionContainsComponent> combined, List<ValueSetExpansionContainsComponent> union, List<ValueSetExpansionContainsComponent> intersection, String path, ValueSetComparison res) {
|
||||
List<ValueSetExpansionContainsComponent> matchR = new ArrayList<>();
|
||||
for (ValueSetExpansionContainsComponent l : left) {
|
||||
ValueSetExpansionContainsComponent r = findInList(right, l);
|
||||
if (r == null) {
|
||||
union.add(l);
|
||||
combined.getChildren().add(new StructuralMatch<ValueSetExpansionContainsComponent>(l, vm(IssueSeverity.INFORMATION, "Removed from expansion", path)));
|
||||
} else {
|
||||
matchR.add(r);
|
||||
ValueSetExpansionContainsComponent ccU = merge(l, r);
|
||||
ValueSetExpansionContainsComponent ccI = intersect(l, r);
|
||||
union.add(ccU);
|
||||
intersection.add(ccI);
|
||||
StructuralMatch<ValueSetExpansionContainsComponent> sm = new StructuralMatch<ValueSetExpansionContainsComponent>(l, r);
|
||||
compareItem(sm.getMessages(), path, l, r, res);
|
||||
combined.getChildren().add(sm);
|
||||
compareConcepts(l.getContains(), r.getContains(), sm, ccU.getContains(), ccI.getContains(), path+".where(code = '"+l.getCode()+"').contains", res);
|
||||
}
|
||||
}
|
||||
for (ValueSetExpansionContainsComponent r : right) {
|
||||
if (!matchR.contains(r)) {
|
||||
union.add(r);
|
||||
combined.getChildren().add(new StructuralMatch<ValueSetExpansionContainsComponent>(vm(IssueSeverity.INFORMATION, "Added to expansion", path), r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void compareItem(List<ValidationMessage> msgs, String path, ValueSetExpansionContainsComponent l, ValueSetExpansionContainsComponent r, ValueSetComparison res) {
|
||||
compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res);
|
||||
}
|
||||
|
||||
private void compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, ValueSetComparison res) {
|
||||
if (!Utilities.noString(right)) {
|
||||
if (Utilities.noString(left)) {
|
||||
msgs.add(vm(level, "Value for "+name+" added", path));
|
||||
} else if (!left.equals(right)) {
|
||||
if (level != IssueSeverity.NULL) {
|
||||
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+".name", "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
|
||||
}
|
||||
msgs.add(vm(level, name+" changed from left to right", path));
|
||||
}
|
||||
} else if (!Utilities.noString(left)) {
|
||||
msgs.add(vm(level, "Value for "+name+" removed", path));
|
||||
}
|
||||
}
|
||||
|
||||
private ValueSetExpansionContainsComponent findInList(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent item) {
|
||||
for (ValueSetExpansionContainsComponent t : list) {
|
||||
if (t.getSystem().equals(item.getSystem()) && t.getCode().equals(item.getCode())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ValueSetExpansionContainsComponent intersect(ValueSetExpansionContainsComponent l, ValueSetExpansionContainsComponent r) {
|
||||
ValueSetExpansionContainsComponent res = new ValueSetExpansionContainsComponent();
|
||||
if (l.hasAbstract() && r.hasAbstract()) {
|
||||
res.setAbstract(l.getAbstract());
|
||||
}
|
||||
if (l.hasCode() && r.hasCode()) {
|
||||
res.setCode(l.getCode());
|
||||
}
|
||||
if (l.hasSystem() && r.hasSystem()) {
|
||||
res.setSystem(l.getSystem());
|
||||
}
|
||||
if (l.hasVersion() && r.hasVersion()) {
|
||||
res.setVersion(l.getVersion());
|
||||
}
|
||||
if (l.hasDisplay() && r.hasDisplay()) {
|
||||
res.setDisplay(l.getDisplay());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private ValueSetExpansionContainsComponent merge(ValueSetExpansionContainsComponent l, ValueSetExpansionContainsComponent r) {
|
||||
ValueSetExpansionContainsComponent res = new ValueSetExpansionContainsComponent();
|
||||
if (l.hasAbstract()) {
|
||||
res.setAbstract(l.getAbstract());
|
||||
} else if (r.hasAbstract()) {
|
||||
res.setAbstract(r.getAbstract());
|
||||
}
|
||||
if (l.hasCode()) {
|
||||
res.setCode(l.getCode());
|
||||
} else if (r.hasCode()) {
|
||||
res.setCode(r.getCode());
|
||||
}
|
||||
if (l.hasSystem()) {
|
||||
res.setSystem(l.getSystem());
|
||||
} else if (r.hasSystem()) {
|
||||
res.setSystem(r.getSystem());
|
||||
}
|
||||
if (l.hasVersion()) {
|
||||
res.setVersion(l.getVersion());
|
||||
} else if (r.hasVersion()) {
|
||||
res.setVersion(r.getVersion());
|
||||
}
|
||||
if (l.hasDisplay()) {
|
||||
res.setDisplay(l.getDisplay());
|
||||
} else if (r.hasDisplay()) {
|
||||
res.setDisplay(r.getDisplay());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String fhirType() {
|
||||
return "ValueSet";
|
||||
}
|
||||
|
||||
public XhtmlNode renderCompose(ValueSetComparison csc, String id, String prefix) throws FHIRException, IOException {
|
||||
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "comparison"), false);
|
||||
TableModel model = gen.new TableModel(id, true);
|
||||
model.setAlternating(true);
|
||||
model.getTitles().add(gen.new Title(null, null, "Item", "The type of item being compared", null, 100));
|
||||
model.getTitles().add(gen.new Title(null, null, "Property", "The system for the concept", null, 100, 2));
|
||||
model.getTitles().add(gen.new Title(null, null, "Value", "The display for the concept", null, 200, 2));
|
||||
model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
|
||||
for (StructuralMatch<Element> t : csc.getIncludes().getChildren()) {
|
||||
addComposeRow(gen, model.getRows(), t, "include");
|
||||
}
|
||||
for (StructuralMatch<Element> t : csc.getExcludes().getChildren()) {
|
||||
addComposeRow(gen, model.getRows(), t, "exclude");
|
||||
}
|
||||
return gen.generate(model, prefix, 0, null);
|
||||
}
|
||||
|
||||
private void addComposeRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, String name) {
|
||||
Row r = gen.new Row();
|
||||
rows.add(r);
|
||||
r.getCells().add(gen.new Cell(null, null, name, null, null));
|
||||
if (t.hasLeft() && t.hasRight()) {
|
||||
ConceptSetComponent csL = (ConceptSetComponent) t.getLeft();
|
||||
ConceptSetComponent csR = (ConceptSetComponent) t.getRight();
|
||||
// we assume both have systems
|
||||
if (csL.getSystem().equals(csR.getSystem())) {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).span(2).center());
|
||||
} else {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
r.getCells().add(gen.new Cell(null, null, csR.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
}
|
||||
|
||||
if (csL.hasVersion() && csR.hasVersion()) {
|
||||
if (csL.getVersion().equals(csR.getVersion())) {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).span(2).center());
|
||||
} else {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
}
|
||||
} else if (csL.hasVersion()) {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null));
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
|
||||
} else if (csR.hasVersion()) {
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
|
||||
r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null));
|
||||
} else {
|
||||
r.getCells().add(missingCell(gen).span(2).center());
|
||||
}
|
||||
|
||||
} else if (t.hasLeft()) {
|
||||
r.setColor(COLOR_NO_ROW_RIGHT);
|
||||
ConceptSetComponent cs = (ConceptSetComponent) t.getLeft();
|
||||
r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null));
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null));
|
||||
r.getCells().add(missingCell(gen));
|
||||
} else {
|
||||
r.setColor(COLOR_NO_ROW_LEFT);
|
||||
ConceptSetComponent cs = (ConceptSetComponent) t.getRight();
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null));
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null));
|
||||
}
|
||||
r.getCells().add(cellForMessages(gen, t.getMessages()));
|
||||
for (StructuralMatch<Element> c : t.getChildren()) {
|
||||
if (c.either() instanceof ConceptReferenceComponent) {
|
||||
addSetConceptRow(gen, r.getSubRows(), c);
|
||||
} else {
|
||||
addSetFilterRow(gen, r.getSubRows(), c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addSetConceptRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t) {
|
||||
Row r = gen.new Row();
|
||||
rows.add(r);
|
||||
r.getCells().add(gen.new Cell(null, null, "Concept", null, null));
|
||||
if (t.hasLeft() && t.hasRight()) {
|
||||
ConceptReferenceComponent csL = (ConceptReferenceComponent) t.getLeft();
|
||||
ConceptReferenceComponent csR = (ConceptReferenceComponent) t.getRight();
|
||||
// we assume both have codes
|
||||
if (csL.getCode().equals(csR.getCode())) {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getCode(), null, null).span(2).center());
|
||||
} else {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getCode(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
r.getCells().add(gen.new Cell(null, null, csR.getCode(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
}
|
||||
|
||||
if (csL.hasDisplay() && csR.hasDisplay()) {
|
||||
if (csL.getDisplay().equals(csR.getDisplay())) {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getDisplay(), null, null).span(2).center());
|
||||
} else {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
r.getCells().add(gen.new Cell(null, null, csR.getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
}
|
||||
} else if (csL.hasDisplay()) {
|
||||
r.getCells().add(gen.new Cell(null, null, csL.getDisplay(), null, null));
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
|
||||
} else if (csR.hasDisplay()) {
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
|
||||
r.getCells().add(gen.new Cell(null, null, csR.getDisplay(), null, null));
|
||||
} else {
|
||||
r.getCells().add(missingCell(gen).span(2).center());
|
||||
}
|
||||
|
||||
} else if (t.hasLeft()) {
|
||||
r.setColor(COLOR_NO_ROW_RIGHT);
|
||||
ConceptReferenceComponent cs = (ConceptReferenceComponent) t.getLeft();
|
||||
r.getCells().add(gen.new Cell(null, null, cs.getCode(), null, null));
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(gen.new Cell(null, null, cs.hasDisplay() ? "Version: "+cs.getDisplay() : "", null, null));
|
||||
r.getCells().add(missingCell(gen));
|
||||
} else {
|
||||
r.setColor(COLOR_NO_ROW_LEFT);
|
||||
ConceptReferenceComponent cs = (ConceptReferenceComponent) t.getRight();
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(gen.new Cell(null, null, cs.getCode(), null, null));
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(gen.new Cell(null, null, cs.hasDisplay() ? "Version: "+cs.getDisplay() : "", null, null));
|
||||
}
|
||||
r.getCells().add(cellForMessages(gen, t.getMessages()));
|
||||
|
||||
}
|
||||
|
||||
private void addSetFilterRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t) {
|
||||
// Row r = gen.new Row();
|
||||
// rows.add(r);
|
||||
// r.getCells().add(gen.new Cell(null, null, "Filter", null, null));
|
||||
// if (t.hasLeft() && t.hasRight()) {
|
||||
// ConceptSetComponent csL = (ConceptSetComponent) t.getLeft();
|
||||
// ConceptSetComponent csR = (ConceptSetComponent) t.getRight();
|
||||
// // we assume both have systems
|
||||
// if (csL.getSystem().equals(csR.getSystem())) {
|
||||
// r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).span(2).center());
|
||||
// } else {
|
||||
// r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
// r.getCells().add(gen.new Cell(null, null, csR.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
// }
|
||||
//
|
||||
// if (csL.hasVersion() && csR.hasVersion()) {
|
||||
// if (csL.getVersion().equals(csR.getVersion())) {
|
||||
// r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).span(2).center());
|
||||
// } else {
|
||||
// r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
// r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
// }
|
||||
// } else if (csL.hasVersion()) {
|
||||
// r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null));
|
||||
// r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
|
||||
// } else if (csR.hasVersion()) {
|
||||
// r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
|
||||
// r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null));
|
||||
// } else {
|
||||
// r.getCells().add(missingCell(gen).span(2).center());
|
||||
// }
|
||||
//
|
||||
// } else if (t.hasLeft()) {
|
||||
// r.setColor(COLOR_NO_ROW_RIGHT);
|
||||
// ConceptSetComponent cs = (ConceptSetComponent) t.getLeft();
|
||||
// r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null));
|
||||
// r.getCells().add(missingCell(gen));
|
||||
// r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null));
|
||||
// r.getCells().add(missingCell(gen));
|
||||
// } else {
|
||||
// r.setColor(COLOR_NO_ROW_LEFT);
|
||||
// ConceptSetComponent cs = (ConceptSetComponent) t.getRight();
|
||||
// r.getCells().add(missingCell(gen));
|
||||
// r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null));
|
||||
// r.getCells().add(missingCell(gen));
|
||||
// r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null));
|
||||
// }
|
||||
// r.getCells().add(gen.new Cell(null, null, t.getError(), null, null));
|
||||
|
||||
}
|
||||
|
||||
public XhtmlNode renderExpansion(ValueSetComparison csc, String id, String prefix) throws IOException {
|
||||
if (csc.getExpansion() == null) {
|
||||
XhtmlNode p = new XhtmlNode(NodeType.Element, "p");
|
||||
p.tx("Unable to generate expansion - see errors");
|
||||
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());
|
||||
boolean hasAbstract = findAbstract(csc.getExpansion());
|
||||
boolean hasInactive = findInactive(csc.getExpansion());
|
||||
|
||||
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "comparison"), false);
|
||||
TableModel model = gen.new TableModel(id, true);
|
||||
model.setAlternating(true);
|
||||
if (hasSystem) {
|
||||
model.getTitles().add(gen.new Title(null, null, "System", "The code for the concept", null, 100));
|
||||
}
|
||||
model.getTitles().add(gen.new Title(null, null, "Code", "The system for the concept", null, 100));
|
||||
model.getTitles().add(gen.new Title(null, null, "Display", "The display for the concept", null, 200, 2));
|
||||
// if (hasVersion) {
|
||||
// model.getTitles().add(gen.new Title(null, null, "Version", "The version for the concept", null, 200, 2));
|
||||
// }
|
||||
// if (hasAbstract) {
|
||||
// model.getTitles().add(gen.new Title(null, null, "Abstract", "The abstract flag for the concept", null, 200, 2));
|
||||
// }
|
||||
// if (hasInactive) {
|
||||
// model.getTitles().add(gen.new Title(null, null, "Inactive", "The inactive flag for the concept", null, 200, 2));
|
||||
// }
|
||||
model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
|
||||
for (StructuralMatch<ValueSetExpansionContainsComponent> t : csc.getExpansion().getChildren()) {
|
||||
addExpansionRow(gen, model.getRows(), t, hasSystem, hasVersion, hasAbstract, hasInactive);
|
||||
}
|
||||
return gen.generate(model, prefix, 0, null);
|
||||
}
|
||||
|
||||
private void addExpansionRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ValueSetExpansionContainsComponent> t, boolean hasSystem, boolean hasVersion, boolean hasAbstract, boolean hasInactive) {
|
||||
Row r = gen.new Row();
|
||||
rows.add(r);
|
||||
if (hasSystem) {
|
||||
r.getCells().add(gen.new Cell(null, null, t.either().getSystem(), null, null));
|
||||
}
|
||||
r.getCells().add(gen.new Cell(null, null, t.either().getCode(), null, null));
|
||||
if (t.hasLeft() && t.hasRight()) {
|
||||
if (t.getLeft().hasDisplay() && t.getRight().hasDisplay()) {
|
||||
if (t.getLeft().getDisplay().equals(t.getRight().getDisplay())) {
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2).center());
|
||||
} else {
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
|
||||
}
|
||||
} else if (t.getLeft().hasDisplay()) {
|
||||
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null));
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
|
||||
} else if (t.getRight().hasDisplay()) {
|
||||
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
|
||||
r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null));
|
||||
} else {
|
||||
r.getCells().add(missingCell(gen).span(2).center());
|
||||
}
|
||||
|
||||
} else if (t.hasLeft()) {
|
||||
r.setColor(COLOR_NO_ROW_RIGHT);
|
||||
r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
|
||||
r.getCells().add(missingCell(gen));
|
||||
} else {
|
||||
r.setColor(COLOR_NO_ROW_LEFT);
|
||||
r.getCells().add(missingCell(gen));
|
||||
r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
|
||||
}
|
||||
r.getCells().add(cellForMessages(gen, t.getMessages()));
|
||||
for (StructuralMatch<ValueSetExpansionContainsComponent> c : t.getChildren()) {
|
||||
addExpansionRow(gen, r.getSubRows(), c, hasSystem, hasVersion, hasAbstract, hasInactive);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean getSystemVaries(StructuralMatch<ValueSetExpansionContainsComponent> list, String system) {
|
||||
for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) {
|
||||
if (t.hasLeft() && !system.equals(t.getLeft().getSystem())) {
|
||||
return true;
|
||||
}
|
||||
if (t.hasRight() && !system.equals(t.getRight().getSystem())) {
|
||||
return true;
|
||||
}
|
||||
if (getSystemVaries(t, system)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean findInactive(StructuralMatch<ValueSetExpansionContainsComponent> list) {
|
||||
for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) {
|
||||
if (t.hasLeft() && t.getLeft().getInactive()) {
|
||||
return true;
|
||||
}
|
||||
if (t.hasRight() && t.getRight().getInactive()) {
|
||||
return true;
|
||||
}
|
||||
if (findInactive(t)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean findAbstract(StructuralMatch<ValueSetExpansionContainsComponent> list) {
|
||||
for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) {
|
||||
if (t.hasLeft() && t.getLeft().getAbstract()) {
|
||||
return true;
|
||||
}
|
||||
if (t.hasRight() && t.getRight().getAbstract()) {
|
||||
return true;
|
||||
}
|
||||
if (findAbstract(t)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean findVersion(StructuralMatch<ValueSetExpansionContainsComponent> list) {
|
||||
for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) {
|
||||
if (t.hasLeft() && t.getLeft().hasVersion()) {
|
||||
return true;
|
||||
}
|
||||
if (t.hasRight() && t.getRight().hasVersion()) {
|
||||
return true;
|
||||
}
|
||||
if (findVersion(t)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -245,5 +245,8 @@ public class TextFile {
|
|||
return streamToString(new ByteArrayInputStream(bs));
|
||||
}
|
||||
|
||||
|
||||
public static void streamToFile(InputStream stream, String filename) throws IOException {
|
||||
byte[] cnt = streamToBytes(stream);
|
||||
bytesToFile(cnt, filename);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1265,6 +1265,18 @@ public class Utilities {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean startsWithInList(String s, String... list) {
|
||||
if (s == null) {
|
||||
return false;
|
||||
}
|
||||
for (String l : list) {
|
||||
if (s.startsWith(l)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -204,9 +204,12 @@ public class PackageCacheManager {
|
|||
}
|
||||
|
||||
public void loadFromFolder(String packagesFolder) throws IOException {
|
||||
for (File f : new File(packagesFolder).listFiles()) {
|
||||
if (f.getName().endsWith(".tgz")) {
|
||||
temporaryPackages.add(NpmPackage.fromPackage(new FileInputStream(f)));
|
||||
File[] files = new File(packagesFolder).listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (f.getName().endsWith(".tgz")) {
|
||||
temporaryPackages.add(NpmPackage.fromPackage(new FileInputStream(f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,10 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
|
|||
private static final String BACKGROUND_ALT_COLOR = "#F7F7F7";
|
||||
public static boolean ACTIVE_TABLES = false;
|
||||
|
||||
public enum TextAlignment {
|
||||
LEFT, CENTER, RIGHT;
|
||||
}
|
||||
|
||||
private static Map<String, String> files = new HashMap<String, String>();
|
||||
|
||||
private class Counter {
|
||||
|
@ -201,6 +205,7 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
|
|||
private List<Piece> pieces = new ArrayList<HierarchicalTableGenerator.Piece>();
|
||||
private String cellStyle;
|
||||
protected int span = 1;
|
||||
private TextAlignment alignment = TextAlignment.LEFT;
|
||||
|
||||
public Cell() {
|
||||
|
||||
|
@ -373,6 +378,10 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
|
|||
span = value;
|
||||
return this;
|
||||
}
|
||||
public Cell center() {
|
||||
alignment = TextAlignment.CENTER;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
package org.hl7.fhir.comparison.tests;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_10_50;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_14_50;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_30_50;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_40_50;
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.exceptions.PathEngineException;
|
||||
import org.hl7.fhir.r5.comparison.CodeSystemComparer;
|
||||
import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison;
|
||||
import org.hl7.fhir.r5.comparison.ValueSetComparer;
|
||||
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
|
||||
import org.hl7.fhir.r5.conformance.ProfileUtilities;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
|
||||
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r5.formats.JsonParser;
|
||||
import org.hl7.fhir.r5.formats.XmlParser;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.Constants;
|
||||
import org.hl7.fhir.r5.model.FhirPublication;
|
||||
import org.hl7.fhir.r5.model.Patient;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.TypeDetails;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.test.utils.TestingUtilities;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.cache.NpmPackage;
|
||||
import org.hl7.fhir.utilities.cache.PackageCacheManager;
|
||||
import org.hl7.fhir.utilities.cache.ToolsVersion;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
|
||||
import org.hl7.fhir.validation.ValidationEngine;
|
||||
import org.hl7.fhir.validation.instance.InstanceValidator;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.thymeleaf.util.IWritableCharSequence;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ComparisonTests {
|
||||
|
||||
public final static boolean PRINT_OUTPUT_TO_CONSOLE = true;
|
||||
|
||||
public static Stream<Arguments> data() throws IOException {
|
||||
String contents = TestingUtilities.loadTestResource("comparison", "manifest.json");
|
||||
|
||||
Map<String, JsonObject> examples = new HashMap<String, JsonObject>();
|
||||
manifest = (JsonObject) new com.google.gson.JsonParser().parse(contents);
|
||||
for (Entry<String, JsonElement> e : manifest.getAsJsonObject("test-cases").entrySet()) {
|
||||
examples.put(e.getKey(), e.getValue().getAsJsonObject());
|
||||
}
|
||||
|
||||
List<String> names = new ArrayList<String>(examples.size());
|
||||
names.addAll(examples.keySet());
|
||||
Collections.sort(names);
|
||||
|
||||
List<Arguments> objects = new ArrayList<>();
|
||||
for (String id : names) {
|
||||
objects.add(Arguments.of(id, examples.get(id)));
|
||||
}
|
||||
return objects.stream();
|
||||
}
|
||||
|
||||
private static JsonObject manifest;
|
||||
private static IWorkerContext context;
|
||||
private JsonObject content;
|
||||
|
||||
private static final String DEF_TX = "http://tx.fhir.org";
|
||||
private static final String HEADER = "<html><link href=\"http://hl7.org/fhir/fhir.css\" rel=\"stylesheet\"/><body>";
|
||||
private static final String BREAK = "<hr/>";
|
||||
private static final String FOOTER = "</body></html>";
|
||||
|
||||
@ParameterizedTest(name = "{index}: id {0}")
|
||||
@MethodSource("data")
|
||||
public void test(String name, JsonObject content) throws Exception {
|
||||
this.content = content;
|
||||
|
||||
if (content.has("use-test") && !content.get("use-test").getAsBoolean())
|
||||
return;
|
||||
|
||||
if (context == null) {
|
||||
System.out.println("---- Load R5 ----------------------------------------------------------------");
|
||||
context = TestingUtilities.context();
|
||||
}
|
||||
|
||||
if (!new File(Utilities.path("[tmp]", "comparison")).exists()) {
|
||||
System.out.println("---- Set up Output ----------------------------------------------------------");
|
||||
Utilities.createDirectory(Utilities.path("[tmp]", "comparison"));
|
||||
PackageCacheManager pcm = new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
|
||||
NpmPackage npm = pcm.loadPackage("hl7.fhir.pubpack", "0.0.4");
|
||||
for (String f : npm.list("other")) {
|
||||
TextFile.streamToFile(npm.load("other", f), Utilities.path("[tmp]", "comparison", f));
|
||||
}
|
||||
}
|
||||
System.out.println("---- " + name + " ----------------------------------------------------------------");
|
||||
CanonicalResource left = load("left");
|
||||
CanonicalResource right = load("right");
|
||||
|
||||
if (left instanceof CodeSystem && right instanceof CodeSystem) {
|
||||
CodeSystemComparer cs = new CodeSystemComparer(context);
|
||||
CodeSystemComparison csc = cs.compare((CodeSystem) left, (CodeSystem) right);
|
||||
Assertions.assertTrue(csc.getUnion().getConcept().size() > csc.getIntersection().getConcept().size());
|
||||
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());
|
||||
|
||||
String xmle = new XhtmlComposer(true).compose(cs.renderErrors(csc));
|
||||
String xml1 = new XhtmlComposer(true).compose(cs.renderMetadata(csc, "", ""));
|
||||
String xml2 = new XhtmlComposer(true).compose(cs.renderConcepts(csc, "", ""));
|
||||
TextFile.stringToFile(HEADER+hd("Messages")+xmle+BREAK+hd("Metadata")+xml1+BREAK+hd("Concepts")+xml2+FOOTER, Utilities.path("[tmp]", "comparison", name+".html"));
|
||||
checkOutcomes(csc.getMessages(), content);
|
||||
} else if (left instanceof ValueSet && right instanceof ValueSet) {
|
||||
ValueSetComparer cs = new ValueSetComparer(context);
|
||||
ValueSetComparison csc = cs.compare((ValueSet) left, (ValueSet) 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());
|
||||
|
||||
String xmle = new XhtmlComposer(true).compose(cs.renderErrors(csc));
|
||||
String xml1 = new XhtmlComposer(true).compose(cs.renderMetadata(csc, "", ""));
|
||||
String xml2 = new XhtmlComposer(true).compose(cs.renderCompose(csc, "", ""));
|
||||
String xml3 = new XhtmlComposer(true).compose(cs.renderExpansion(csc, "", ""));
|
||||
TextFile.stringToFile(HEADER+hd("Messages")+xmle+BREAK+hd("Metadata")+xml1+BREAK+hd("Definition")+xml2+BREAK+hd("Expansion")+xml3+FOOTER, Utilities.path("[tmp]", "comparison", name+".html"));
|
||||
checkOutcomes(csc.getMessages(), content);
|
||||
} else {
|
||||
throw new FHIRException("Can't compare "+left.fhirType()+" to "+right.fhirType());
|
||||
}
|
||||
}
|
||||
|
||||
private String hd(String text) {
|
||||
return "<h2>"+text+"</h2>\r\n";
|
||||
}
|
||||
|
||||
private CanonicalResource load(String name) throws IOException {
|
||||
JsonObject details = content.getAsJsonObject(name);
|
||||
String src = TestingUtilities.loadTestResource("comparison", details.get("source").getAsString());
|
||||
return (CanonicalResource) loadResource(details.get("source").getAsString(), src, details.get("version").getAsString());
|
||||
}
|
||||
|
||||
public Resource loadResource(String filename, String contents, String ver) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException {
|
||||
try (InputStream inputStream = IOUtils.toInputStream(contents, Charsets.UTF_8)) {
|
||||
if (filename.contains(".json")) {
|
||||
if (Constants.VERSION.equals(ver) || "5.0".equals(ver))
|
||||
return new JsonParser().parse(inputStream);
|
||||
else if (VersionUtilities.isR3Ver(ver))
|
||||
return VersionConvertor_30_50.convertResource(new org.hl7.fhir.dstu3.formats.JsonParser().parse(inputStream), false);
|
||||
else if (VersionUtilities.isR2BVer(ver))
|
||||
return VersionConvertor_14_50.convertResource(new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(inputStream));
|
||||
else if (VersionUtilities.isR2Ver(ver))
|
||||
return VersionConvertor_10_50.convertResource(new org.hl7.fhir.dstu2.formats.JsonParser().parse(inputStream));
|
||||
else if (VersionUtilities.isR4Ver(ver))
|
||||
return VersionConvertor_40_50.convertResource(new org.hl7.fhir.r4.formats.JsonParser().parse(inputStream));
|
||||
else
|
||||
throw new FHIRException("unknown version " + ver);
|
||||
} else {
|
||||
if (Constants.VERSION.equals(ver) || "5.0".equals(ver))
|
||||
return new XmlParser().parse(inputStream);
|
||||
else if (VersionUtilities.isR3Ver(ver))
|
||||
return VersionConvertor_30_50.convertResource(new org.hl7.fhir.dstu3.formats.XmlParser().parse(inputStream), false);
|
||||
else if (VersionUtilities.isR2BVer(ver))
|
||||
return VersionConvertor_14_50.convertResource(new org.hl7.fhir.dstu2016may.formats.XmlParser().parse(inputStream));
|
||||
else if (VersionUtilities.isR2Ver(ver))
|
||||
return VersionConvertor_10_50.convertResource(new org.hl7.fhir.dstu2.formats.XmlParser().parse(inputStream));
|
||||
else if (VersionUtilities.isR4Ver(ver))
|
||||
return VersionConvertor_40_50.convertResource(new org.hl7.fhir.r4.formats.XmlParser().parse(inputStream));
|
||||
else
|
||||
throw new FHIRException("unknown version " + ver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkOutcomes(List<ValidationMessage> errors, JsonObject focus) {
|
||||
JsonObject output = focus.getAsJsonObject("output");
|
||||
int ec = 0;
|
||||
int wc = 0;
|
||||
int hc = 0;
|
||||
List<String> errLocs = new ArrayList<>();
|
||||
for (ValidationMessage vm : errors) {
|
||||
if (vm.getLevel() == IssueSeverity.FATAL || vm.getLevel() == IssueSeverity.ERROR) {
|
||||
ec++;
|
||||
if (PRINT_OUTPUT_TO_CONSOLE) {
|
||||
System.out.println(vm.getDisplay());
|
||||
}
|
||||
errLocs.add(vm.getLocation());
|
||||
}
|
||||
if (vm.getLevel() == IssueSeverity.WARNING) {
|
||||
wc++;
|
||||
if (PRINT_OUTPUT_TO_CONSOLE) {
|
||||
System.out.println(vm.getDisplay());
|
||||
}
|
||||
}
|
||||
if (vm.getLevel() == IssueSeverity.INFORMATION) {
|
||||
hc++;
|
||||
if (PRINT_OUTPUT_TO_CONSOLE) {
|
||||
System.out.println(vm.getDisplay());
|
||||
}
|
||||
}
|
||||
}
|
||||
Assertions.assertEquals(output.get("errorCount").getAsInt(), ec, "Expected " + Integer.toString(output.get("errorCount").getAsInt()) + " errors, but found " + Integer.toString(ec) + ".");
|
||||
if (output.has("warningCount"))
|
||||
Assertions.assertEquals(output.get("warningCount").getAsInt(), wc, "Expected " + Integer.toString(output.get("warningCount").getAsInt()) + " warnings, but found " + Integer.toString(wc) + ".");
|
||||
if (output.has("infoCount"))
|
||||
Assertions.assertEquals(output.get("infoCount").getAsInt(), hc, "Expected " + Integer.toString(output.get("infoCount").getAsInt()) + " hints, but found " + Integer.toString(hc) + ".");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue