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