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