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