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