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