001package org.hl7.fhir.r5.comparison;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Date;
006import java.util.List;
007
008import org.hl7.fhir.exceptions.DefinitionException;
009import org.hl7.fhir.exceptions.FHIRException;
010import org.hl7.fhir.exceptions.FHIRFormatError;
011import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison;
012import org.hl7.fhir.r5.context.IWorkerContext;
013import org.hl7.fhir.r5.model.BooleanType;
014import org.hl7.fhir.r5.model.CanonicalResource;
015import org.hl7.fhir.r5.model.CanonicalType;
016import org.hl7.fhir.r5.model.CapabilityStatement;
017import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
018import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
019import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceOperationComponent;
020import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
021import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestSecurityComponent;
022import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent;
023import org.hl7.fhir.r5.model.CodeType;
024import org.hl7.fhir.r5.model.CodeableConcept;
025import org.hl7.fhir.r5.model.Coding;
026import org.hl7.fhir.r5.model.Element;
027import org.hl7.fhir.r5.model.Extension;
028import org.hl7.fhir.r5.model.PrimitiveType;
029import org.hl7.fhir.r5.model.Resource;
030import org.hl7.fhir.r5.model.StructureDefinition;
031import org.hl7.fhir.r5.utils.ToolingExtensions;
032import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
033import org.hl7.fhir.utilities.Utilities;
034import org.hl7.fhir.utilities.i18n.RenderingI18nContext;
035import org.hl7.fhir.utilities.validation.ValidationMessage;
036import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
037import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
038import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
039import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
040import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
041import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
042import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
043import org.hl7.fhir.utilities.xhtml.XhtmlNode;
044
045@MarkedToMoveToAdjunctPackage
046public class CapabilityStatementComparer extends CanonicalResourceComparer {
047
048  
049  public class CapabilityStatementComparison extends CanonicalResourceComparison<CapabilityStatement> {
050
051    private StructuralMatch<Element> combined;                                             
052
053    public CapabilityStatementComparison(CapabilityStatement left, CapabilityStatement right) {
054      super(left, right);
055      combined = new StructuralMatch<Element>(); // base
056    }
057      
058    public StructuralMatch<Element> getCombined() {
059      return combined;
060    }
061
062    @Override
063    protected String abbreviation() {
064      return "cps";
065    }
066
067    @Override
068    protected String summary() {
069      return "CapabilityStatement: "+left.present()+" vs "+right.present();
070    }
071
072    @Override
073    protected String fhirType() {
074      return "CapabilityStatement";
075    }
076
077    @Override
078    protected void countMessages(MessageCounts cnts) {
079      super.countMessages(cnts);
080      combined.countMessages(cnts);
081    }
082  }
083
084  public CapabilityStatementComparer(ComparisonSession session) {
085    super(session);
086  }
087
088  public CapabilityStatementComparison compare(CapabilityStatement left, CapabilityStatement right) throws DefinitionException, FHIRFormatError, IOException {    
089    if (left == null)
090      throw new DefinitionException("No CapabilityStatement provided (left)");
091    if (right == null)
092      throw new DefinitionException("No CapabilityStatement provided (right)");
093    
094    
095    CapabilityStatementComparison res = new CapabilityStatementComparison(left, right);
096    session.identify(res);
097    CapabilityStatement cs = new CapabilityStatement();
098    res.setUnion(cs);
099    session.identify(cs);
100    cs.setName("Union"+left.getName()+"And"+right.getName());
101    cs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
102    cs.setStatus(left.getStatus());
103    cs.setDate(new Date());
104
105    CapabilityStatement cs1 = new CapabilityStatement();
106    res.setIntersection(cs1);
107    session.identify(cs1);
108    cs1.setName("Intersection"+left.getName()+"And"+right.getName());
109    cs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
110    cs1.setStatus(left.getStatus());
111    cs1.setDate(new Date());
112
113    compareMetadata(left, right, res.getMetadata(), res, new ArrayList<>(), right);
114    comparePrimitives("kind", left.getKindElement(), right.getKindElement(), res.getMetadata(), IssueSeverity.ERROR, res);
115    compareCanonicalList("instantiates", left.getInstantiates(), right.getInstantiates(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getInstantiates(), cs1.getInstantiates());
116    compareCanonicalList("imports", left.getImports(), right.getImports(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getImports(), cs1.getImports());
117    comparePrimitives("software.name", left.getSoftware().getNameElement(), right.getSoftware().getNameElement(), res.getMetadata(), IssueSeverity.ERROR, res);
118    comparePrimitives("software.version", left.getSoftware().getVersionElement(), right.getSoftware().getVersionElement(), res.getMetadata(), IssueSeverity.ERROR, res);
119    comparePrimitives("software.releaseDate", left.getSoftware().getReleaseDateElement(), right.getSoftware().getReleaseDateElement(), res.getMetadata(), IssueSeverity.ERROR, res);
120    comparePrimitives("implementation.description", left.getImplementation().getDescriptionElement(), right.getImplementation().getDescriptionElement(), res.getMetadata(), IssueSeverity.ERROR, res);
121    comparePrimitives("implementation.url", left.getImplementation().getUrlElement(), right.getImplementation().getUrlElement(), res.getMetadata(), IssueSeverity.ERROR, res);
122    comparePrimitives("fhirVersion", left.getFhirVersionElement(), right.getFhirVersionElement(), res.getMetadata(), IssueSeverity.ERROR, res);
123    compareCodeList("format", left.getFormat(), right.getFormat(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getFormat(), cs1.getFormat());
124    compareCodeList("patchFormat", left.getPatchFormat(), right.getPatchFormat(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getPatchFormat(), cs1.getPatchFormat());
125    compareCanonicalList("implementationGuide", left.getImplementationGuide(), right.getImplementationGuide(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getImplementationGuide(), cs1.getImplementationGuide());
126
127
128    compareRests(left.getRest(), right.getRest(), res.getCombined(), res.getUnion().getRest(), res.getIntersection().getRest(), res.getUnion(), res.getIntersection(), res, "CapabilityStatement.rest");
129    return res;
130  }
131
132  private void compareRests(List<CapabilityStatementRestComponent> left, List<CapabilityStatementRestComponent> right, StructuralMatch<Element> combined, List<CapabilityStatementRestComponent> union, List<CapabilityStatementRestComponent> intersection, CapabilityStatement csU, CapabilityStatement csI, CapabilityStatementComparison res, String path) throws DefinitionException, FHIRFormatError, IOException {
133    List<CapabilityStatementRestComponent> matchR = new ArrayList<>();
134    for (CapabilityStatementRestComponent l : left) {
135      CapabilityStatementRestComponent r = findInList(right, l);
136      if (r == null) {
137        union.add(l);
138        combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", path)));
139      } else {
140        matchR.add(r);
141        CapabilityStatementRestComponent cdM = merge(l, r, res);
142        CapabilityStatementRestComponent cdI = intersect(l, r, res);
143        union.add(cdM);
144        intersection.add(cdI);
145        StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
146        compare(sm, l, r, path+".where(mode='"+l.getMode()+"')", res);
147        combined.getChildren().add(sm);
148        compareRestSecurity(l, r, sm, cdM.getSecurity(), cdI.getSecurity(), csU, csI, res, path+".security");
149        compareRestResources(l, r, sm, cdM, cdI, csU, csI, res, path+".resource");
150        compareSearchParams(combined, l.getSearchParam(), r.getSearchParam(), path, res, cdM.getSearchParam(), cdI.getSearchParam());
151        compareOperations(combined, l.getOperation(), r.getOperation(), path, res, cdM.getOperation(), cdI.getOperation());
152        compareItemPropertyList(sm, "compartment", l.getCompartment(), r.getCompartment(), path, res, cdM.getCompartment(), cdI.getCompartment(), IssueSeverity.ERROR);
153      }
154    }
155    for (CapabilityStatementRestComponent r : right) {
156      if (!matchR.contains(r)) {
157        union.add(r);
158        combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));        
159      }
160    }
161  }
162
163  private CapabilityStatementRestComponent findInList(List<CapabilityStatementRestComponent> list, CapabilityStatementRestComponent item) {
164    for (CapabilityStatementRestComponent t : list) {
165      if (t.getMode().equals(item.getMode())) {
166        return t;
167      }
168    }
169    return null;
170  }
171
172  private void compare(StructuralMatch<Element> sm, CapabilityStatementRestComponent l, CapabilityStatementRestComponent r, String path, CapabilityStatementComparison res) {
173    compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.WARNING, res);
174  }
175
176  private void compareRestSecurity(CapabilityStatementRestComponent l, CapabilityStatementRestComponent r, StructuralMatch<Element> smp, CapabilityStatementRestSecurityComponent merge, CapabilityStatementRestSecurityComponent intersect, CapabilityStatement csU, CapabilityStatement csI, CapabilityStatementComparison res, String path) {
177    CapabilityStatementRestSecurityComponent ls = l.hasSecurity() ? l.getSecurity() : null;
178    CapabilityStatementRestSecurityComponent rs = r.hasSecurity() ? r.getSecurity() : null;
179    
180    StructuralMatch<Element> sm = new StructuralMatch<Element>(ls, rs);
181    smp.getChildren().add(sm);
182    compareBooleans(path, sm.getMessages(), l.getSecurity().getCorsElement(), r.getSecurity().getCorsElement(), "security.cors", IssueSeverity.WARNING, res);
183    compareStrings(path, sm.getMessages(), l.getSecurity().getDescription(), r.getSecurity().getDescription(), "security.description", IssueSeverity.INFORMATION, res);
184    compareRestSecurityService(ls, rs, sm, merge, intersect, csU, csI, res, path+".security");    
185  }
186
187  private void compareRestSecurityService(CapabilityStatementRestSecurityComponent left, CapabilityStatementRestSecurityComponent right, StructuralMatch<Element> combined, CapabilityStatementRestSecurityComponent union, CapabilityStatementRestSecurityComponent intersection, CapabilityStatement csU, CapabilityStatement csI, CapabilityStatementComparison res, String path) {
188    List<CodeableConcept> matchR = new ArrayList<>();
189    if (left != null) {
190      for (CodeableConcept l : left.getService()) {
191        CodeableConcept r = findInList(right.getService(), l);
192        if (r == null) {
193          union.getService().add(l);
194          combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", path)));
195        } else {
196          matchR.add(r);
197          CodeableConcept cdM = CodeableConcept.merge(l, r);
198          CodeableConcept cdI = CodeableConcept.intersect(l, r);
199          union.getService().add(cdM);
200          intersection.getService().add(cdI);
201          StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
202          compare(sm, l, r, path, res);
203          combined.getChildren().add(sm);
204        }
205      }
206    }
207    if (right != null) {
208      for (CodeableConcept r : right.getService()) {
209        if (!matchR.contains(r)) {
210          union.getService().add(r);
211          combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));        
212        }
213      }
214    }
215  }
216  
217
218  private void compare(StructuralMatch<Element> sm, CodeableConcept l, CodeableConcept r, String path, CapabilityStatementComparison res) {
219    compareStrings(path, sm.getMessages(), l.getText(), r.getText(), "text", IssueSeverity.INFORMATION, res);
220    List<Coding> matches = new ArrayList<>();
221    for (Coding lc : l.getCoding()) {
222      boolean m = false;
223      for (Coding rc : r.getCoding()) {
224        if (lc.matches(rc)) {
225          matches.add(rc);
226          m = true;
227        }
228      }
229      if (!m) {
230        sm.getMessages().add(vmI(IssueSeverity.INFORMATION, "Value for "+gen(lc)+" removed", path));        
231      }      
232    }
233    for (Coding rc : r.getCoding()) {
234      if (!matches.contains(rc)) {
235        sm.getMessages().add(vmI(IssueSeverity.INFORMATION, "Value for "+gen(rc)+" added", path));        
236      }
237    }    
238  }
239
240  private CodeableConcept findInList(List<CodeableConcept> list, CodeableConcept item) {
241    for (CodeableConcept t : list) {
242      if (t.matches(item)) {
243        return t;
244      }
245    }
246    return null;
247  }
248  
249  private void compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CapabilityStatementComparison res) {
250    if (!Utilities.noString(right)) {
251      if (Utilities.noString(left)) {
252        msgs.add(vmI(level, "Value for "+name+" added", path));
253      } else if (!left.equals(right)) {
254        if (level != IssueSeverity.NULL) {
255          res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
256        }
257        msgs.add(vmI(level, name+" changed from left to right", path));
258      }
259    } else if (!Utilities.noString(left)) {
260      msgs.add(vmI(level, "Value for "+name+" removed", path));
261    }
262  }
263
264  private void compareExpectations(StructuralMatch<Element> combined, Element left, Element right, String path, CapabilityStatementComparison res, Element union, Element intersection) {
265    List<Extension> l = left.getExtensionsByUrl(ToolingExtensions.EXT_CAP_STMT_EXPECT);
266    List<Extension> r = right.getExtensionsByUrl(ToolingExtensions.EXT_CAP_STMT_EXPECT);
267    if (l.size() == 1 || r.size() == 1) {
268      if (l.size() == 0) {
269        union.addExtension(r.get(0).copy());
270        combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this expectation", path), r.get(0)));        
271      } else if (r.size() == 0) {
272        union.addExtension(l.get(0).copy());
273        combined.getChildren().add(new StructuralMatch<Element>(l.get(0), vmI(IssueSeverity.INFORMATION, "Removed this expectation", path)));              
274      } else if (l.size() == 1 && r.size() == 1) {
275        StructuralMatch<Element> sm = new StructuralMatch<Element>(l.get(0), r.get(0));
276        combined.getChildren().add(sm);
277        String ls = l.get(0).getValue().primitiveValue();
278        String rs = r.get(0).getValue().primitiveValue();
279        if (ls.equals(rs)) {
280          union.addExtension(l.get(0).copy());
281          intersection.addExtension(l.get(0).copy());
282        } else {
283          sm.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+".extension('http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation')", "Changed value for expectation: '"+ls+"' vs '"+rs+"'", IssueSeverity.WARNING));
284          String lowest = lower(ls, rs) ? ls : rs;
285          String highest = lower(ls, rs) ? rs : ls;
286          union.addExtension(ToolingExtensions.EXT_CAP_STMT_EXPECT, new CodeType(lowest));
287          intersection.addExtension(ToolingExtensions.EXT_CAP_STMT_EXPECT, new CodeType(highest));
288        }
289      }
290    }
291  }
292
293  private boolean lower(String ls, String rs) {
294    if (ls.equals("MAY")) {
295      return true;
296    }
297    if (ls.equals("SHALL")) {
298      return false;
299    }
300    if (rs.equals("MAY")) {
301      return false;
302    }
303    if (rs.equals("SHALL")) {
304      return true;
305    }
306    return false;
307  }
308
309  private void compareBooleans(String path, List<ValidationMessage> msgs, BooleanType left, BooleanType right, String name, IssueSeverity level, CapabilityStatementComparison res) {
310    if (!right.isEmpty()) {
311      if (left.isEmpty()) {
312        msgs.add(vmI(level, "Value for "+name+" added", path));
313      } else if (left.getValue() != right.getValue()) {
314        if (level != IssueSeverity.NULL) {
315          res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
316        }
317        msgs.add(vmI(level, name+" changed from left to right", path));
318      }
319    } else if (!left.isEmpty()) {
320      msgs.add(vmI(level, "Value for "+name+" removed", path));
321    }
322  }
323
324  private CapabilityStatementRestComponent merge(CapabilityStatementRestComponent l, CapabilityStatementRestComponent r, CapabilityStatementComparison res) {
325    CapabilityStatementRestComponent cd = l.copy();
326    if (!l.hasDocumentation() && r.hasDocumentation()) {
327      cd.setDocumentation(r.getDocumentation());
328    }
329    if (r.hasSecurity()) {
330      if (!l.getSecurity().hasCors() && r.getSecurity().hasCors()) {
331        cd.getSecurity().setCors(r.getSecurity().getCors());
332      }
333      mergeCodeableConcepts(cd.getSecurity().getService(), r.getSecurity().getService());  
334      if (!l.getSecurity().hasDescription() && r.getSecurity().hasDescription()) {
335        cd.getSecurity().setDescription(r.getSecurity().getDescription());
336      }
337    }
338    return cd;
339  }
340
341  private void mergeCodeableConcepts(List<CodeableConcept> tgt, List<CodeableConcept> src) {
342    for (CodeableConcept cd : src) {
343      boolean add = true;
344      for (CodeableConcept t : tgt) {
345        if (t.matches(cd)) {
346          add = false;
347        }
348      }
349      if (add) {
350        tgt.add(cd.copy());
351      }
352    }    
353  }
354
355  private CapabilityStatementRestComponent intersect(CapabilityStatementRestComponent l, CapabilityStatementRestComponent r, CapabilityStatementComparison res) {
356    CapabilityStatementRestComponent cd = l.copy();
357    if (l.hasDocumentation() && !r.hasDocumentation()) {
358      cd.setDocumentation(null);
359    }
360    if (!r.hasSecurity()) {
361      cd.setSecurity(null);
362    } else {
363      if (!r.getSecurity().hasCors()) {
364        cd.getSecurity().setCorsElement(null);
365      }
366      intersectCodeableConcepts(cd.getSecurity().getService(), r.getSecurity().getService());  
367      if (!r.getSecurity().hasDescription()) {
368        cd.getSecurity().setDescription(null);
369      }
370    }
371    return cd;
372  }
373  
374  private void intersectCodeableConcepts(List<CodeableConcept> tgt, List<CodeableConcept> src) {
375    List<CodeableConcept> toRemove = new ArrayList<CodeableConcept>();
376    for (CodeableConcept cd : src) {
377      boolean remove = false;
378      for (CodeableConcept t : tgt) {
379        if (t.matches(cd)) {
380          remove = true;
381        }
382      }
383      if (remove) {
384        toRemove.add(cd);
385      }
386    }    
387    tgt.removeAll(toRemove);
388  }
389
390  private void compareRestResources(CapabilityStatementRestComponent left, CapabilityStatementRestComponent right, StructuralMatch<Element> combined, CapabilityStatementRestComponent union, CapabilityStatementRestComponent intersection, CapabilityStatement csU, CapabilityStatement csI, CapabilityStatementComparison res, String path) throws DefinitionException, FHIRFormatError, IOException {
391    List<CapabilityStatementRestResourceComponent> matchR = new ArrayList<>();
392    for (CapabilityStatementRestResourceComponent l : left.getResource()) {
393      CapabilityStatementRestResourceComponent r = findInList(right.getResource(), l);
394      if (r == null) {
395        union.getResource().add(l);
396        combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", path)));
397      } else {
398        matchR.add(r);
399        CapabilityStatementRestResourceComponent cdM = mergeRestResource(l, r);
400        CapabilityStatementRestResourceComponent cdI = intersectRestResource(l, r);
401        union.getResource().add(cdM);
402        intersection.getResource().add(cdI);
403        StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
404        compareRestResource(sm, l, r, path, res, cdM, cdI);
405        combined.getChildren().add(sm);
406      }
407    }
408    for (CapabilityStatementRestResourceComponent r : right.getResource()) {
409      if (!matchR.contains(r)) {
410        union.getResource().add(r);
411        combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));        
412      }
413    }
414  }
415  
416  private void compareRestResource(StructuralMatch<Element> sm, CapabilityStatementRestResourceComponent l, CapabilityStatementRestResourceComponent r, String path, CapabilityStatementComparison res, CapabilityStatementRestResourceComponent union, CapabilityStatementRestResourceComponent intersection) throws DefinitionException, FHIRFormatError, IOException {
417    compareProfiles(path, sm, l.getProfileElement(), r.getProfileElement(), res, union, intersection);
418    // todo: supported profiles
419    compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.INFORMATION, res);
420    compareExpectations(sm, l, r, path, res, union, intersection);    
421    compareRestResourceInteractions(sm, l, r, path, res, union, intersection);
422    compareItemProperty(sm, "versioning", l.getVersioningElement(), r.getVersioningElement(), path, res, union.getVersioningElement(), intersection.getVersioningElement(), IssueSeverity.WARNING);
423    compareItemProperty(sm, "readHistory", l.getReadHistoryElement(), r.getReadHistoryElement(), path, res, union.getReadHistoryElement(), intersection.getReadHistoryElement(), IssueSeverity.INFORMATION);
424    compareItemProperty(sm, "updateCreate", l.getUpdateCreateElement(), r.getUpdateCreateElement(), path, res, union.getUpdateCreateElement(), intersection.getUpdateCreateElement(), IssueSeverity.WARNING);
425    compareItemProperty(sm, "conditionalCreate", l.getConditionalCreateElement(), r.getConditionalCreateElement(), path, res, union.getConditionalCreateElement(), intersection.getConditionalCreateElement(), IssueSeverity.WARNING);
426    compareItemProperty(sm, "conditionalRead", l.getConditionalReadElement(), r.getConditionalReadElement(), path, res, union.getConditionalReadElement(), intersection.getConditionalReadElement(), IssueSeverity.WARNING);
427    compareItemProperty(sm, "conditionalUpdate", l.getConditionalUpdateElement(), r.getConditionalUpdateElement(), path, res, union.getConditionalUpdateElement(), intersection.getConditionalUpdateElement(), IssueSeverity.WARNING);
428    compareItemProperty(sm, "conditionalDelete", l.getConditionalDeleteElement(), r.getConditionalDeleteElement(), path, res, union.getConditionalDeleteElement(), intersection.getConditionalDeleteElement(), IssueSeverity.WARNING);
429    compareItemPropertyList(sm, "referencePolicy", l.getReferencePolicy(), r.getReferencePolicy(), path, res, union.getReferencePolicy(), intersection.getReferencePolicy(), IssueSeverity.WARNING);
430    compareItemPropertyList(sm, "searchInclude", l.getSearchInclude(), r.getSearchInclude(), path, res, union.getSearchInclude(), intersection.getSearchInclude(), IssueSeverity.WARNING);
431    compareItemPropertyList(sm, "searchRevInclude", l.getSearchRevInclude(), r.getSearchRevInclude(), path, res, union.getSearchRevInclude(), intersection.getSearchRevInclude(), IssueSeverity.WARNING);
432    compareSearchParams(sm, l.getSearchParam(), r.getSearchParam(), path, res, union.getSearchParam(), intersection.getSearchParam());
433    compareOperations(sm, l.getOperation(), r.getOperation(), path, res, union.getOperation(), intersection.getOperation());
434  }
435
436  private void compareProfiles(String path, StructuralMatch<Element> combined, CanonicalType left, CanonicalType right, CapabilityStatementComparison res, CapabilityStatementRestResourceComponent union, CapabilityStatementRestResourceComponent intersection) throws DefinitionException, FHIRFormatError, IOException {
437    if (!left.hasValue() && !right.hasValue()) {
438      // nothing in this case 
439    } else if (!left.hasValue()) {
440      // the intersection is anything in right. The union is everything (or nothing, in this case)
441      intersection.setProfileElement(right.copy());
442      combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.WARNING, "Added this profile", path), right).setName("profile"));        
443    } else if (!right.hasValue()) {
444      // the intersection is anything in right. The union is everything (or nothing, in this case)
445      intersection.setProfileElement(left.copy());
446      combined.getChildren().add(new StructuralMatch<Element>(left, vmI(IssueSeverity.WARNING, "Removed this profile", path)).setName("profile"));        
447    } else {
448      // profiles on both sides...
449      StructureDefinition sdLeft = session.getContextLeft().fetchResource(StructureDefinition.class, left.getValue());
450      StructureDefinition sdRight = session.getContextRight().fetchResource(StructureDefinition.class, right.getValue());
451      if (sdLeft == null && sdRight == null) {
452        combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.ERROR, "Cannot compare profiles because neither is known", path)).setName("profile"));        
453      } else if (sdLeft == null) {
454        combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.ERROR, "Cannot compare profiles because '"+left.getValue()+"' is not known", path)).setName("profile"));        
455      } else if (sdRight == null) {
456        combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.ERROR, "Cannot compare profiles because '"+right.getValue()+"' is not known", path)).setName("profile"));                
457      } else if (sdLeft.getUrl().equals(sdRight.getUrl())) {
458        intersection.setProfileElement(left.copy());
459        union.setProfileElement(left.copy());
460        combined.getChildren().add(new StructuralMatch<Element>(left, right).setName("profile"));                
461      } else if (profileInherits(sdLeft, sdRight, session.getContextLeft())) {
462        // if left inherits from right:
463        intersection.setProfileElement(left.copy());
464        union.setProfileElement(right.copy());
465        combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.WARNING, "Changed this profile to a broader profile", path)).setName("profile"));                
466      } else if (profileInherits(sdRight, sdLeft, session.getContextRight())) {
467        intersection.setProfileElement(right.copy());
468        union.setProfileElement(left.copy());
469        combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.WARNING, "Changed this profile to a narrower one", path)).setName("profile"));                
470      } else {
471        combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.WARNING, "Different", path)).setName("profile"));                
472        ProfileComparison pc = (ProfileComparison) session.compare(sdLeft, sdRight);
473        intersection.setProfile(pc.getIntersection().getUrl());
474        union.setProfile(pc.getUnion().getUrl());
475      }
476    }
477  }
478
479  private boolean profileInherits(StructureDefinition sdFocus, StructureDefinition sdOther, IWorkerContext ctxt) {
480    while (sdFocus != null) {
481      if (sdFocus.getUrl().equals(sdOther.getUrl()) && sdFocus.getVersion().equals(sdOther.getVersion())) {
482        return true;
483      }
484      sdFocus = ctxt.fetchResource(StructureDefinition.class, sdFocus.getBaseDefinition(), sdFocus);
485    }
486    return false;
487  }
488
489  private <T> void compareItemProperty(StructuralMatch<Element> combined, String name, PrimitiveType<T> left, PrimitiveType<T> right, String path, CapabilityStatementComparison res, PrimitiveType<T> union, PrimitiveType<T> intersection, IssueSeverity issueSeverity) {
490    if (!left.isEmpty() || !right.isEmpty()) {
491      if (left.isEmpty()) {
492        union.copyValues(right);
493        combined.getChildren().add(new StructuralMatch<Element>(vmI(issueSeverity, "Added this "+name, path), right).setName(name));        
494      } else if (right.isEmpty()) {
495        union.copyValues(left);
496        combined.getChildren().add(new StructuralMatch<Element>(left, vmI(issueSeverity, "Removed this expectation", path)).setName(name));              
497      } else {
498        StructuralMatch<Element> sm = new StructuralMatch<Element>(left, right).setName(name);
499        combined.getChildren().add(sm);
500        String ls = left.primitiveValue();
501        String rs = right.primitiveValue();
502        if (ls.equals(rs)) {
503          union.copyValues(left);
504          intersection.copyValues(left);
505        } else {
506          sm.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+ls+"' vs '"+rs+"'", issueSeverity));
507          union.copyValues(left);
508          intersection.copyValues(left);
509        }
510        compareExpectations(sm, left, right, path, res, union, intersection);    
511      }
512    }
513  }
514
515  private <T extends Element> void compareItemPropertyList(StructuralMatch<Element> combined, String name, List<T> left, List<T> right, String path, CapabilityStatementComparison res, List<T> union, List<T> intersection, IssueSeverity issueSeverity) {
516    List<T> matchR = new ArrayList<>();
517    for (T l : left) {
518      T r = findInListT(right, l);
519      if (r == null) {
520        union.add(l);
521        combined.getChildren().add(new StructuralMatch<Element>(l, vmI(issueSeverity, "Removed this "+name, path)).setName(name));
522      } else {
523        matchR.add(r);
524        union.add(l);
525        intersection.add(l);
526        StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r).setName(name);
527        combined.getChildren().add(sm);
528      }
529    }
530    for (T r : right) {
531      if (!matchR.contains(r)) {
532        union.add(r);
533        combined.getChildren().add(new StructuralMatch<Element>(vmI(issueSeverity, "Added this "+name, path), r).setName(name));        
534      }
535    }
536  }
537
538  private <T extends Element> T findInListT(List<T> list, T item) {
539    for (T t : list) {
540      if (t.equalsDeep(item)) {
541        return t;
542      }
543    }
544    return null;
545  }
546
547
548  private CapabilityStatementRestResourceComponent mergeRestResource(CapabilityStatementRestResourceComponent l, CapabilityStatementRestResourceComponent r) {
549    CapabilityStatementRestResourceComponent res = l.copy();
550    // todo: compare profiles, not just copy
551    if (!l.hasProfile() && r.hasProfile()) {
552      res.setProfile(r.getProfile());
553    }
554    if (!l.hasDocumentation() && r.hasDocumentation()) {
555      res.setDocumentation(r.getDocumentation());
556    }
557    return res;
558  }
559
560  private CapabilityStatementRestResourceComponent intersectRestResource(CapabilityStatementRestResourceComponent l, CapabilityStatementRestResourceComponent r) {
561    CapabilityStatementRestResourceComponent res = new CapabilityStatementRestResourceComponent();
562    res.setType(l.getType());
563    // todo: compare profiles, not just copy
564    if (l.hasProfile() && l.getProfile().equals(r.getProfile())) {
565      res.setProfile(l.getProfile());
566    }
567    if (l.hasDocumentation() && l.getDocumentation().equals(r.getDocumentation())) {
568      res.setDocumentation(l.getDocumentation());
569    }
570    return res;
571  }
572
573  private CapabilityStatementRestResourceComponent findInList(List<CapabilityStatementRestResourceComponent> list, CapabilityStatementRestResourceComponent item) {
574    for (CapabilityStatementRestResourceComponent t : list) {
575      if (t.hasType() && t.getType().equals(item.getType())) {
576        return t;
577      }
578    }
579    return null;
580  }
581
582  private void compareRestResourceInteractions(StructuralMatch<Element> combined, CapabilityStatementRestResourceComponent left, CapabilityStatementRestResourceComponent right, String path, CapabilityStatementComparison res, CapabilityStatementRestResourceComponent union, CapabilityStatementRestResourceComponent intersection) {
583    List<ResourceInteractionComponent> matchR = new ArrayList<>();
584    for (ResourceInteractionComponent l : left.getInteraction()) {
585      ResourceInteractionComponent r = findInList(right.getInteraction(), l);
586      if (r == null) {
587        union.getInteraction().add(l);
588        combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", path)));
589      } else {
590        matchR.add(r);
591        ResourceInteractionComponent cdM = mergeRestResourceInteractions(l, r);
592        ResourceInteractionComponent cdI = intersectRestResourceInteractions(l, r);
593        union.getInteraction().add(cdM);
594        intersection.getInteraction().add(cdI);
595        StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
596        compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.INFORMATION, res);
597        compareExpectations(sm, l, r, path, res, union, intersection);    
598        combined.getChildren().add(sm);
599      }
600    }
601    for (ResourceInteractionComponent r : right.getInteraction()) {
602      if (!matchR.contains(r)) {
603        union.getInteraction().add(r);
604        combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));        
605      }
606    }
607  }
608
609  private ResourceInteractionComponent mergeRestResourceInteractions(ResourceInteractionComponent l, ResourceInteractionComponent r) {
610    ResourceInteractionComponent res = l.copy();
611    if (!res.hasDocumentation() && r.hasDocumentation()) {
612      res.setDocumentation(r.getDocumentation());
613    }
614    return res;
615  }
616
617  private ResourceInteractionComponent intersectRestResourceInteractions(ResourceInteractionComponent l, ResourceInteractionComponent r) {
618    ResourceInteractionComponent res = l.copy();
619    if (res.hasDocumentation() && !r.hasDocumentation()) {
620      res.setDocumentation(null);
621    }
622    return res;
623  }
624
625  private ResourceInteractionComponent findInList(List<ResourceInteractionComponent> list, ResourceInteractionComponent item) {
626    for (ResourceInteractionComponent t : list) {
627      if (t.hasCode() && t.getCode().equals(item.getCode())) {
628        return t;
629      }
630    }
631    return null;
632  }
633
634
635  private void compareSearchParams(StructuralMatch<Element> combined, List<CapabilityStatementRestResourceSearchParamComponent> left,  List<CapabilityStatementRestResourceSearchParamComponent> right, String path, CapabilityStatementComparison res,  List<CapabilityStatementRestResourceSearchParamComponent> union, List<CapabilityStatementRestResourceSearchParamComponent> intersection) {
636    List<CapabilityStatementRestResourceSearchParamComponent> matchR = new ArrayList<>();
637    for (CapabilityStatementRestResourceSearchParamComponent l : left) {
638      CapabilityStatementRestResourceSearchParamComponent r = findInList(right, l);
639      if (r == null) {
640        union.add(l);
641        combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this Search Parameter", path)));
642      } else {
643        matchR.add(r);
644        CapabilityStatementRestResourceSearchParamComponent cdM = mergeSearchParams(l, r);
645        CapabilityStatementRestResourceSearchParamComponent cdI = intersectSearchParams(l, r);
646        union.add(cdM);
647        intersection.add(cdI);
648        StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
649        compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.INFORMATION, res);
650        compareItemProperty(sm, "type", l.getTypeElement(), r.getTypeElement(), path, res, cdM.getTypeElement(), cdI.getTypeElement(), IssueSeverity.ERROR);
651        compareItemProperty(sm, "definition", l.getDefinitionElement(), r.getDefinitionElement(), path, res, cdM.getDefinitionElement(), cdI.getDefinitionElement(), IssueSeverity.ERROR);
652        compareExpectations(sm, l, r, path, res, cdM, cdI);    
653        combined.getChildren().add(sm);
654      }
655    }
656    for (CapabilityStatementRestResourceSearchParamComponent r : right) {
657      if (!matchR.contains(r)) {
658        union.add(r);
659        combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this Search Parameter", path), r));        
660      }
661    }
662  }
663  
664  private CapabilityStatementRestResourceSearchParamComponent mergeSearchParams(CapabilityStatementRestResourceSearchParamComponent l, CapabilityStatementRestResourceSearchParamComponent r) {
665    CapabilityStatementRestResourceSearchParamComponent res = l.copy();
666    if (!res.hasDocumentation() && r.hasDocumentation()) {
667      res.setDocumentation(r.getDocumentation());
668    }
669    return res;
670  }
671
672  private CapabilityStatementRestResourceSearchParamComponent intersectSearchParams(CapabilityStatementRestResourceSearchParamComponent l, CapabilityStatementRestResourceSearchParamComponent r) {
673    CapabilityStatementRestResourceSearchParamComponent res = new CapabilityStatementRestResourceSearchParamComponent();
674    res.setName(l.getName());
675    if (l.hasDocumentation() && r.hasDocumentation()) {
676      res.setDocumentation(l.getDocumentation());
677    }
678    return res;
679  }
680
681  private CapabilityStatementRestResourceSearchParamComponent findInList(List<CapabilityStatementRestResourceSearchParamComponent> list, CapabilityStatementRestResourceSearchParamComponent item) {
682    for (CapabilityStatementRestResourceSearchParamComponent t : list) {
683      if (t.hasName() && t.getName().equals(item.getName())) {
684        return t;
685      }
686    }
687    return null;
688  }
689
690
691  private void compareOperations(StructuralMatch<Element> combined, List<CapabilityStatementRestResourceOperationComponent> left,  List<CapabilityStatementRestResourceOperationComponent> right, String path, CapabilityStatementComparison res,  List<CapabilityStatementRestResourceOperationComponent> union, List<CapabilityStatementRestResourceOperationComponent> intersection) {
692    List<CapabilityStatementRestResourceOperationComponent> matchR = new ArrayList<>();
693    for (CapabilityStatementRestResourceOperationComponent l : left) {
694      CapabilityStatementRestResourceOperationComponent r = findInList(right, l);
695      if (r == null) {
696        union.add(l);
697        combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this Search Parameter", path)));
698      } else {
699        matchR.add(r);
700        CapabilityStatementRestResourceOperationComponent cdM = mergeOperations(l, r);
701        CapabilityStatementRestResourceOperationComponent cdI = intersectOperations(l, r);
702        union.add(cdM);
703        intersection.add(cdI);
704        StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
705        compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.INFORMATION, res);
706        compareItemProperty(sm, "definition", l.getDefinitionElement(), r.getDefinitionElement(), path, res, cdM.getDefinitionElement(), cdI.getDefinitionElement(), IssueSeverity.ERROR);
707        compareExpectations(sm, l, r, path, res, cdM, cdI);    
708        combined.getChildren().add(sm);
709      }
710    }
711    for (CapabilityStatementRestResourceOperationComponent r : right) {
712      if (!matchR.contains(r)) {
713        union.add(r);
714        combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this Search Parameter", path), r));        
715      }
716    }
717  }
718  
719  private CapabilityStatementRestResourceOperationComponent mergeOperations(CapabilityStatementRestResourceOperationComponent l, CapabilityStatementRestResourceOperationComponent r) {
720    CapabilityStatementRestResourceOperationComponent res = l.copy();
721    if (!res.hasDocumentation() && r.hasDocumentation()) {
722      res.setDocumentation(r.getDocumentation());
723    }
724    return res;
725  }
726
727  private CapabilityStatementRestResourceOperationComponent intersectOperations(CapabilityStatementRestResourceOperationComponent l, CapabilityStatementRestResourceOperationComponent r) {
728    CapabilityStatementRestResourceOperationComponent res = new CapabilityStatementRestResourceOperationComponent();
729    res.setName(l.getName());
730    if (l.hasDocumentation() && r.hasDocumentation()) {
731      res.setDocumentation(l.getDocumentation());
732    }
733    return res;
734  }
735
736  private CapabilityStatementRestResourceOperationComponent findInList(List<CapabilityStatementRestResourceOperationComponent> list, CapabilityStatementRestResourceOperationComponent item) {
737    for (CapabilityStatementRestResourceOperationComponent t : list) {
738      if (t.hasName() && t.getName().equals(item.getName())) {
739        return t;
740      }
741    }
742    return null;
743  }
744
745  
746  // 6 columns: path | left value | left doco | right value | right doco | comments
747  public XhtmlNode renderStatements(CapabilityStatementComparison comparison, String id, String prefix) throws FHIRException, IOException {
748    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false, "c");
749    TableModel model = gen.new TableModel(id, true);
750    model.setAlternating(true);
751    model.getTitles().add(gen.new Title(null, null, "Type", "The type of item", null, 100));
752    model.getTitles().add(gen.new Title(null, null, "Left Value", "The left value for the item", null, 200, 1));
753    model.getTitles().add(gen.new Title(null, null, "Left Doco", "The left documentation for the item", null, 200, 1));
754    model.getTitles().add(gen.new Title(null, null, "Right Value", "The right value for the item", null, 200, 1));
755    model.getTitles().add(gen.new Title(null, null, "Right Doco", "The right documentation for the item", null, 200, 1));
756    model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
757    for (StructuralMatch<Element> t : comparison.getCombined().getChildren()) {
758      addRow(gen, model.getRows(), t, comparison);
759    }
760    return gen.generate(model, prefix, 0, null);
761  }
762
763  private void addRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
764    Row r = null;
765    if (t.either() instanceof CapabilityStatementRestComponent) {
766      r = addRestRow(gen, rows, t, comparison);
767    } else if (t.either() instanceof CapabilityStatementRestSecurityComponent) {
768      r = addRestSecurityRow(gen, rows, t, comparison);
769    } else if (t.either() instanceof CapabilityStatementRestResourceComponent) {
770      r = addRestResourceRow(gen, rows, t, comparison);
771    } else if (t.either() instanceof ResourceInteractionComponent) {
772      r = addRestResourceInteractionRow(gen, rows, t, comparison);
773    } else if (t.either() instanceof CapabilityStatementRestResourceSearchParamComponent) {
774      r = addRestSearchParamRow(gen, rows, t, comparison);
775    } else if (t.either() instanceof CapabilityStatementRestResourceOperationComponent) {
776      r = addRestOperationRow(gen, rows, t, comparison);
777    } else if (t.either() instanceof CodeableConcept) {
778      r = addRestSecurityServiceRow(gen, rows, t, comparison);
779    } else if (t.either() instanceof Extension) {
780      r = addExtensionRow(gen, rows, t, comparison);
781    } else if (t.either() instanceof PrimitiveType) {
782      r = addPrimitiveTypeRow(gen, rows, t, comparison);
783    } else {
784      throw new Error("Not Done Yet: "+t.either().getClass().getName());
785    }
786    for (StructuralMatch<Element> c : t.getChildren()) {
787      addRow(gen, r.getSubRows(), c, comparison);
788    }
789  }
790
791  private Row addRestRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
792    Row r = gen.new Row();
793    rows.add(r);
794    r.getCells().add(gen.new Cell(null, null, "mode", null, null));
795    CapabilityStatementRestComponent left = t.hasLeft() ? (CapabilityStatementRestComponent) t.getLeft() : null;
796    CapabilityStatementRestComponent right = t.hasRight() ? (CapabilityStatementRestComponent) t.getRight() : null;
797    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getMode().toCode() : "", null, null), left != null ? left.getMode().toCode() : null, right != null ? right.getMode().toCode() : null, true));
798    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
799    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getMode().toCode() : "", null, null), left != null ? left.getMode().toCode() : null, right != null ? right.getMode().toCode() : null, false));
800    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
801    r.getCells().add(cellForMessages(gen, t.getMessages()));
802    return r;
803  }
804  
805  private Row addRestSecurityRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
806    Row r = gen.new Row();
807    rows.add(r);
808    r.getCells().add(gen.new Cell(null, null, "security", null, null));
809    CapabilityStatementRestSecurityComponent left = t.hasLeft() ? (CapabilityStatementRestSecurityComponent) t.getLeft() : null;
810    CapabilityStatementRestSecurityComponent right = t.hasRight() ? (CapabilityStatementRestSecurityComponent) t.getRight() : null;
811    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getCorsElement().primitiveValue() : "", null, null), left != null ? left.getCorsElement().primitiveValue() : null, right != null ? right.getCorsElement().primitiveValue() : null, true));
812    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDescription() : "", null, null), left != null ? left.getDescription() : null, right != null ? right.getDescription() : null, true));
813    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getCorsElement().primitiveValue() : "", null, null), left != null ? left.getCorsElement().primitiveValue() : null, right != null ? right.getCorsElement().primitiveValue() : null, false));
814    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDescription() : "", null, null), left != null ? left.getDescription() : null, right != null ? right.getDescription() : null, true));
815    r.getCells().add(cellForMessages(gen, t.getMessages()));
816    return r;
817  }
818
819  private Row addRestResourceRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
820    Row r = gen.new Row();
821    rows.add(r);
822    r.getCells().add(gen.new Cell(null, null, "resource", null, null));
823    CapabilityStatementRestResourceComponent left = t.hasLeft() ? (CapabilityStatementRestResourceComponent) t.getLeft() : null;
824    CapabilityStatementRestResourceComponent right = t.hasRight() ? (CapabilityStatementRestResourceComponent) t.getRight() : null;
825    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getType() : "", null, null), left != null ? left.getType() : null, right != null ? right.getType() : null, true));
826    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
827    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getType() : "", null, null), left != null ? left.getType() : null, right != null ? right.getType() : null, false));
828    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
829    r.getCells().add(cellForMessages(gen, t.getMessages()));
830    return r;
831  }
832
833  private Row addRestSearchParamRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
834    Row r = gen.new Row();
835    rows.add(r);
836    r.getCells().add(gen.new Cell(null, null, "searchParam", null, null));
837    CapabilityStatementRestResourceSearchParamComponent left = t.hasLeft() ? (CapabilityStatementRestResourceSearchParamComponent) t.getLeft() : null;
838    CapabilityStatementRestResourceSearchParamComponent right = t.hasRight() ? (CapabilityStatementRestResourceSearchParamComponent) t.getRight() : null;
839    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getName() : "", null, null), left != null ? left.getName() : null, right != null ? right.getName() : null, true));
840    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
841    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getName() : "", null, null), left != null ? left.getName() : null, right != null ? right.getName() : null, false));
842    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
843    r.getCells().add(cellForMessages(gen, t.getMessages()));
844    return r;
845  }
846
847  private Row addRestOperationRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
848    Row r = gen.new Row();
849    rows.add(r);
850    r.getCells().add(gen.new Cell(null, null, "operation", null, null));
851    CapabilityStatementRestResourceOperationComponent left = t.hasLeft() ? (CapabilityStatementRestResourceOperationComponent) t.getLeft() : null;
852    CapabilityStatementRestResourceOperationComponent right = t.hasRight() ? (CapabilityStatementRestResourceOperationComponent) t.getRight() : null;
853    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getName() : "", null, null), left != null ? left.getName() : null, right != null ? right.getName() : null, true));
854    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
855    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getName() : "", null, null), left != null ? left.getName() : null, right != null ? right.getName() : null, false));
856    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
857    r.getCells().add(cellForMessages(gen, t.getMessages()));
858    return r;
859  }
860
861  private Row addRestSecurityServiceRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
862    Row r = gen.new Row();
863    rows.add(r);
864    r.getCells().add(gen.new Cell(null, null, "service", null, null));
865    CodeableConcept left = t.hasLeft() ? (CodeableConcept) t.getLeft() : null;
866    CodeableConcept right = t.hasRight() ? (CodeableConcept) t.getRight() : null;
867    r.getCells().add(style(gen.new Cell(null, null, left != null ? gen(left) : "", null, null), left != null ? gen(left) : null, right != null ? gen(right) : null, true));
868    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getText() : "", null, null), left != null ? left.getText() : null, right != null ? right.getText() : null, true));
869    r.getCells().add(style(gen.new Cell(null, null, right != null ? gen(right) : "", null, null), left != null ? gen(left) : null, right != null ? gen(right) : null, false));
870    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getText() : "", null, null), left != null ? left.getText() : null, right != null ? right.getText() : null, true));
871    r.getCells().add(cellForMessages(gen, t.getMessages()));
872    return r;
873  }
874  
875  private Row addRestResourceInteractionRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
876    Row r = gen.new Row();
877    rows.add(r);
878    r.getCells().add(gen.new Cell(null, null, "interaction", null, null));
879    ResourceInteractionComponent left = t.hasLeft() ? (ResourceInteractionComponent) t.getLeft() : null;
880    ResourceInteractionComponent right = t.hasRight() ? (ResourceInteractionComponent) t.getRight() : null;
881    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getCode().getDisplay() : "", null, null), left != null ? left.getCode().getDisplay() : null, right != null ? right.getCode().getDisplay() : null, true));
882    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
883    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getCode().getDisplay() : "", null, null), left != null ? left.getCode().getDisplay() : null, right != null ? right.getCode().getDisplay() : null, false));
884    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
885    r.getCells().add(cellForMessages(gen, t.getMessages()));
886    return r;
887  }
888
889  private Row addExtensionRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
890    Row r = gen.new Row();
891    rows.add(r);
892    r.getCells().add(gen.new Cell(null, null, "expectation", null, null));
893    Extension left = t.hasLeft() ? (Extension) t.getLeft() : null;
894    Extension right = t.hasRight() ? (Extension) t.getRight() : null;
895    r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getValue().primitiveValue() : "", null, null), left != null ? left.getValue().primitiveValue() : null, right != null ? right.getValue().primitiveValue() : null, true));
896    r.getCells().add(gen.new Cell(null, null, "", null, null));
897    r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getValue().primitiveValue() : "", null, null), left != null ? left.getValue().primitiveValue() : null, right != null ? right.getValue().primitiveValue() : null, false));
898    r.getCells().add(gen.new Cell(null, null, "", null, null));
899    r.getCells().add(cellForMessages(gen, t.getMessages()));
900    return r;
901  }
902  
903  @SuppressWarnings("rawtypes")
904  private Row addPrimitiveTypeRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
905    Row r = gen.new Row();
906    rows.add(r);
907    r.getCells().add(gen.new Cell(null, null, t.getName(), null, null));
908    PrimitiveType left = t.hasLeft() ? (PrimitiveType) t.getLeft() : null;
909    PrimitiveType right = t.hasRight() ? (PrimitiveType) t.getRight() : null;
910    CanonicalResource crL = left == null ? null : (CanonicalResource) session.getContextLeft().fetchResource(Resource.class, left.primitiveValue());
911    CanonicalResource crR = right == null ? null : (CanonicalResource) session.getContextRight().fetchResource(Resource.class, right.primitiveValue());
912    String refL = crL != null && crL.hasWebPath() ? crL.getWebPath() : null;
913    String dispL = crL != null && refL != null ? crL.present() : left == null ? "" : left.primitiveValue(); 
914    String refR = crR != null && crR.hasWebPath() ? crR.getWebPath() : null;
915    String dispR = crR != null && refR != null ? crR.present() : right == null ? "" : right.primitiveValue(); 
916    r.getCells().add(style(gen.new Cell(null, refL, dispL, null, null), left != null ? left.primitiveValue() : null, right != null ? right.primitiveValue() : null, true));
917    r.getCells().add(gen.new Cell(null, null, "", null, null));
918    r.getCells().add(style(gen.new Cell(null, refR, dispR, null, null), left != null ? left.primitiveValue() : null, right != null ? right.primitiveValue() : null, false));
919    r.getCells().add(gen.new Cell(null, null, "", null, null));
920    r.getCells().add(cellForMessages(gen, t.getMessages()));
921    return r;
922  }
923  
924  private Cell style(Cell cell, String left, String right, boolean isLeft) {
925    if (left != null && right != null) {
926      if (!left.equals(right)) {
927        cell.setStyle("background-color: "+COLOR_DIFFERENT);
928      }
929    } else if (left != null) {
930      if (!isLeft) {        
931        cell.setStyle("background-color: "+COLOR_NO_CELL_RIGHT);
932      }
933    } else if (right != null) {        
934      if (isLeft) {        
935        cell.setStyle("background-color: "+COLOR_NO_CELL_LEFT);
936      }
937    }
938    return cell;
939  }
940
941  @Override
942  protected String fhirType() {
943    return "CapabilityStatement";
944  }
945
946}