001package org.hl7.fhir.r5.comparison;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Date;
006import java.util.HashMap;
007import java.util.List;
008import java.util.Map;
009
010import org.hl7.fhir.exceptions.DefinitionException;
011import org.hl7.fhir.exceptions.FHIRException;
012import org.hl7.fhir.r5.model.Base;
013import org.hl7.fhir.r5.model.CodeSystem;
014import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
015import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
016import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
017import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
018import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
019import org.hl7.fhir.utilities.Utilities;
020import org.hl7.fhir.utilities.validation.ValidationMessage;
021import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
022import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
023import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
024import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
025import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
026import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
027import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
028import org.hl7.fhir.utilities.xhtml.XhtmlNode;
029
030public class CodeSystemComparer extends CanonicalResourceComparer {
031
032
033  public class CodeSystemComparison extends CanonicalResourceComparison<CodeSystem> {
034
035    private StructuralMatch<PropertyComponent> properties = new StructuralMatch<PropertyComponent>(); 
036    private StructuralMatch<CodeSystemFilterComponent> filters = new StructuralMatch<CodeSystemFilterComponent>();                                             
037    private StructuralMatch<ConceptDefinitionComponent> combined;                                             
038    private Map<String, String> propMap = new HashMap<>(); // right to left; left retains it's name
039    public CodeSystemComparison(CodeSystem left, CodeSystem right) {
040      super(left, right);
041      combined = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(); // base
042    }
043
044    public Map<String, String> getPropMap() {
045      return propMap;
046    }
047
048    public StructuralMatch<ConceptDefinitionComponent> getCombined() {
049      return combined;
050    }
051
052    public StructuralMatch<PropertyComponent> getProperties() {
053      return properties;
054    }
055
056    public StructuralMatch<CodeSystemFilterComponent> getFilters() {
057      return filters;
058    }
059
060    @Override
061    protected String abbreviation() {
062      return "cs";
063    }
064
065    @Override
066    protected String summary() {
067      String res = "CodeSystem: "+left.present()+" vs "+right.present();
068      String ch = changeSummary();
069      if (ch != null) {
070        res = res + ". "+ch;
071      }
072      return res;
073    }
074
075
076    @Override
077    protected String fhirType() {
078      return "CodeSystem";
079    }
080
081    @Override
082    protected void countMessages(MessageCounts cnts) {
083      super.countMessages(cnts);
084      combined.countMessages(cnts);
085    }
086
087  }
088
089  private CodeSystem right;
090
091  public CodeSystemComparer(ComparisonSession session) {
092    super(session);
093  }
094
095  public CodeSystemComparison compare(CodeSystem left, CodeSystem right) {    
096    if (left == null)
097      throw new DefinitionException("No CodeSystem provided (left)");
098    if (right == null)
099      throw new DefinitionException("No CodeSystem provided (right)");
100    
101    CodeSystemComparison res = new CodeSystemComparison(left, right);
102    session.identify(res);
103    CodeSystem cs = new CodeSystem();
104    res.setUnion(cs);
105    session.identify(cs);
106    cs.setName("Union"+left.getName()+"And"+right.getName());
107    cs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
108    cs.setStatus(left.getStatus());
109    cs.setDate(new Date());
110    for (PropertyComponent pL : left.getProperty()) {
111      cs.addProperty(pL.copy());
112    }
113    for (PropertyComponent pR : left.getProperty()) {
114      PropertyComponent pL = findProperty(left, pR);
115      if (pL == null) {
116        String code = getUniqued(pR.getCode(), cs.getProperty());
117        cs.addProperty(pR.copy().setCode(code));
118      } else {
119        res.getPropMap().put(pR.getCode(), pL.getCode());
120      }
121    }
122
123    CodeSystem cs1 = new CodeSystem();
124    res.setIntersection(cs1);
125    session.identify(cs1);
126    cs1.setName("Intersection"+left.getName()+"And"+right.getName());
127    cs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
128    cs1.setStatus(left.getStatus());
129    cs1.setDate(new Date());
130    cs1.getProperty().addAll(cs.getProperty());
131
132
133    List<String> chMetadata = new ArrayList<>();
134    boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right);
135    if (comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res)) {
136      ch = true;
137      chMetadata.add("versionNeeded");
138    }
139    if (comparePrimitives("compositional", left.getCompositionalElement(), right.getCompositionalElement(), res.getMetadata(), IssueSeverity.WARNING, res)) {
140      ch = true;
141      chMetadata.add("compositional");
142    }
143    res.updatedMetadataState(ch, chMetadata);
144    ch = false;
145    ch = comparePrimitivesWithTracking("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch;
146    ch = comparePrimitivesWithTracking("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch;
147    ch = comparePrimitivesWithTracking("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res, right);
148    
149    ch = compareProperties(left.getProperty(), right.getProperty(), res.getProperties(), res.getUnion().getProperty(), res.getIntersection().getProperty(), res.getUnion(), res.getIntersection(), res, "CodeSystem.property", right) || ch;
150    ch = compareFilters(left.getFilter(), right.getFilter(), res.getFilters(), res.getUnion().getFilter(), res.getIntersection().getFilter(), res.getUnion(), res.getIntersection(), res, "CodeSystem.filter", right) || ch;
151    ch = compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept", right) || ch;
152    res.updateDefinitionsState(ch);
153
154    session.annotate(right, res);
155    return res;
156  }
157
158  private String getUniqued(String code, List<PropertyComponent> list) {
159    int i = 0;
160    while (true) {
161      boolean ok = true;
162      String res = code+(i == 0 ? "" : i);
163      for (PropertyComponent t : list) {
164        if (res.equals(t.getCode())) {
165          ok = false;
166        }
167      }
168      if (ok) {
169        return res;
170      }
171    }
172  }
173
174  private PropertyComponent findProperty(CodeSystem left, PropertyComponent p) {
175    for (PropertyComponent t : left.getProperty()) {
176      if (p.hasUri() && t.hasUri() && p.getUri().equals(t.getUri())) {
177        return t;
178      } else if (!p.hasUri() && !t.hasUri() && p.getCode().equals(t.getCode())) {
179        return t;
180      }
181    }
182    return null;
183  }
184
185  private boolean compareProperties(List<PropertyComponent> left, List<PropertyComponent> right, StructuralMatch<PropertyComponent> combined,
186    List<PropertyComponent> union, List<PropertyComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) {
187    boolean def = false;
188    List<PropertyComponent> matchR = new ArrayList<>();
189    for (PropertyComponent l : left) {
190      PropertyComponent r = findInList(right, l);
191      if (r == null) {
192        union.add(l);
193        res.updateContentState(true);
194        combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
195        session.markDeleted(parent, "concept", l);
196      } else {
197        matchR.add(r);
198        PropertyComponent cdM = merge(l, r, res);
199        PropertyComponent cdI = intersect(l, r, res);
200        union.add(cdM);
201        intersection.add(cdI);
202        StructuralMatch<PropertyComponent> sm = new StructuralMatch<CodeSystem.PropertyComponent>(l, r);
203        if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) {
204          def = true;
205        }
206        combined.getChildren().add(sm);
207      }
208    }
209    for (PropertyComponent r : right) {
210      if (!matchR.contains(r)) {
211        union.add(r);
212        res.updateContentState(true);
213        combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));    
214        session.markAdded(r);     
215      }
216    }
217    return def;
218  }
219
220
221  private boolean compareFilters(List<CodeSystemFilterComponent> left, List<CodeSystemFilterComponent> right, StructuralMatch<CodeSystemFilterComponent> combined,
222    List<CodeSystemFilterComponent> union, List<CodeSystemFilterComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) {
223    boolean def = false;
224    List<CodeSystemFilterComponent> matchR = new ArrayList<>();
225    for (CodeSystemFilterComponent l : left) {
226      CodeSystemFilterComponent r = findInList(right, l);
227      if (r == null) {
228        union.add(l);
229        res.updateContentState(true);
230        combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
231        session.markDeleted(parent, "concept", l);
232      } else {
233        matchR.add(r);
234        CodeSystemFilterComponent cdM = merge(l, r, res);
235        CodeSystemFilterComponent cdI = intersect(l, r, res);
236        union.add(cdM);
237        intersection.add(cdI);
238        StructuralMatch<CodeSystemFilterComponent> sm = new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(l, r);
239        if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) {
240          def = true;
241        }
242        combined.getChildren().add(sm);
243      }
244    }
245    for (CodeSystemFilterComponent r : right) {
246      if (!matchR.contains(r)) {
247        union.add(r);
248        res.updateContentState(true);
249        combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));    
250        session.markAdded(r);     
251      }
252    }
253    return def;
254  }
255
256  
257  private boolean compareConcepts(List<ConceptDefinitionComponent> left, List<ConceptDefinitionComponent> right, StructuralMatch<ConceptDefinitionComponent> combined,
258    List<ConceptDefinitionComponent> union, List<ConceptDefinitionComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) {
259    boolean def = false;
260    List<ConceptDefinitionComponent> matchR = new ArrayList<>();
261    for (ConceptDefinitionComponent l : left) {
262      ConceptDefinitionComponent r = findInList(right, l);
263      if (r == null) {
264        union.add(l);
265        res.updateContentState(true);
266        combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
267        session.markDeleted(parent, "concept", l);
268      } else {
269        matchR.add(r);
270        ConceptDefinitionComponent cdM = merge(l, r, csU.getProperty(), res);
271        ConceptDefinitionComponent cdI = intersect(l, r, res);
272        union.add(cdM);
273        intersection.add(cdI);
274        StructuralMatch<ConceptDefinitionComponent> sm = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, r);
275        if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) {
276          def = true;
277        }
278        combined.getChildren().add(sm);
279        if (compareConcepts(l.getConcept(), r.getConcept(), sm, cdM.getConcept(), cdI.getConcept(), csU, csI, res, path+".where(code='"+l.getCode()+"').concept", r)) {
280          def = true;
281        }
282      }
283    }
284    for (ConceptDefinitionComponent r : right) {
285      if (!matchR.contains(r)) {
286        union.add(r);
287        res.updateContentState(true);
288        combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));    
289        session.markAdded(r);     
290      }
291    }
292    return def;
293  }
294  
295  private CodeSystemFilterComponent findInList(List<CodeSystemFilterComponent> list, CodeSystemFilterComponent item) {
296    for (CodeSystemFilterComponent t : list) {
297      if (t.getCode().equals(item.getCode())) {
298        return t;
299      }
300    }
301    return null;
302  }
303
304
305  private ConceptDefinitionComponent findInList(List<ConceptDefinitionComponent> list, ConceptDefinitionComponent item) {
306    for (ConceptDefinitionComponent t : list) {
307      if (t.getCode().equals(item.getCode())) {
308        return t;
309      }
310    }
311    return null;
312  }
313
314
315  private PropertyComponent findInList(List<PropertyComponent> list, PropertyComponent item) {
316    for (PropertyComponent t : list) {
317      if (t.getCode().equals(item.getCode())) {
318        return t;
319      }
320    }
321    return null;
322  }
323
324  private boolean compare(List<ValidationMessage> msgs, ConceptDefinitionComponent l, ConceptDefinitionComponent r, String path, CodeSystemComparison res, Base parent) {
325    boolean result = false;
326    result = compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res, parent, l.getDisplayElement(), r.getDisplayElement()) || result;
327    result = compareStrings(path, msgs, l.getDefinition(), r.getDefinition(), "definition", IssueSeverity.INFORMATION, res, parent, l.getDefinitionElement(), r.getDefinitionElement()) || result;
328    // todo: designations, properties
329    return result;
330  }
331
332  private boolean compare(List<ValidationMessage> msgs, PropertyComponent l, PropertyComponent r, String path, CodeSystemComparison res, Base parent) {
333    boolean result = false;
334    result = compareStrings(path, msgs, l.getUri(), r.getUri(), "uri", IssueSeverity.WARNING, res, parent, l.getUriElement(), r.getUriElement()) || result;
335    result = compareStrings(path, msgs, l.hasType() ? l.getType().toCode() : null, r.hasType() ? r.getType().toCode() : null, "type", IssueSeverity.ERROR, res, parent, l.getTypeElement(), r.getTypeElement()) || result;
336    result = compareStrings(path, msgs, l.getDescription(), r.getDescription(), "description", IssueSeverity.WARNING, res, parent, l.getDescriptionElement(), r.getDescriptionElement()) || result;
337    return result;
338  }
339
340  private boolean compare(List<ValidationMessage> msgs, CodeSystemFilterComponent l, CodeSystemFilterComponent r, String path, CodeSystemComparison res, Base parent) {
341    boolean result = false;
342    result = compareStrings(path, msgs, l.getDescription(), r.getDescription(), "description", IssueSeverity.WARNING, res, parent, l.getDescriptionElement(), r.getDescriptionElement()) || result;
343// todo: repeating
344//    result = compareStrings(path, msgs, l.hasOperator() ? l.getOperator().toCode() : null, r.hasType() ? r.getType().toCode() : null, "type", IssueSeverity.ERROR, res, parent, l.getTypeElement(), r.getTypeElement()) || result;
345    result = compareStrings(path, msgs, l.getValue(), r.getValue(), "value", IssueSeverity.WARNING, res, parent, l.getValueElement(), r.getValueElement()) || result;
346    return result;
347  }
348
349  private boolean compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CodeSystemComparison res, Base parent, Base l, Base r) {
350    if (!Utilities.noString(right)) {
351      if (Utilities.noString(left)) {
352        msgs.add(vmI(level, "Value for "+name+" added", path));   
353        session.markAdded(r);     
354        return true;
355      } else if (!left.equals(right)) {
356        if (level != IssueSeverity.NULL) {
357          res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
358        }
359        msgs.add(vmI(level, name+" changed from left to right", path));
360        session.markChanged(r, l);   
361        return true;
362      }
363    } else if (!Utilities.noString(left)) {
364      msgs.add(vmI(level, "Value for "+name+" removed", path));
365      session.markDeleted(parent, "concept", l);
366      return true;
367    }
368    return false;
369  }
370
371  private ConceptDefinitionComponent merge(ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
372    ConceptDefinitionComponent cd = l.copy();
373    if (!l.hasDisplay() && r.hasDisplay()) {
374      cd.setDisplay(r.getDisplay());
375    }
376    if (!l.hasDefinition() && r.hasDefinition()) {
377      cd.setDefinition(r.getDefinition());
378    }
379    mergeProps(cd, l, r, destProps, res);
380    mergeDesignations(cd, l, r);
381    return cd;
382  }
383
384  private PropertyComponent merge(PropertyComponent l, PropertyComponent r, CodeSystemComparison res) {
385    PropertyComponent cd = l.copy();
386    if (!l.hasDescription() && r.hasDescription()) {
387      cd.setDescription(r.getDescription());
388    }
389    return cd;
390  }
391
392
393  private CodeSystemFilterComponent merge(CodeSystemFilterComponent l, CodeSystemFilterComponent r, CodeSystemComparison res) {
394    CodeSystemFilterComponent cd = l.copy();
395    if (!l.hasDescription() && r.hasDescription()) {
396      cd.setDescription(r.getDescription());
397    }
398    return cd;
399  }
400
401  private ConceptDefinitionComponent intersect(ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
402    ConceptDefinitionComponent cd = l.copy();
403    if (l.hasDisplay() && !r.hasDisplay()) {
404      cd.setDisplay(null);
405    }
406    if (l.hasDefinition() && !r.hasDefinition()) {
407      cd.setDefinition(null);
408    }
409    intersectProps(cd, l, r, res);
410    //    mergeDesignations(cd, l, r);
411    return cd;
412  }
413
414  private PropertyComponent intersect(PropertyComponent l, PropertyComponent r, CodeSystemComparison res) {
415    PropertyComponent cd = l.copy();
416    if (l.hasDescription() && !r.hasDescription()) {
417      cd.setDescription(null);
418    }
419    return cd;
420  }
421
422  private CodeSystemFilterComponent intersect(CodeSystemFilterComponent l, CodeSystemFilterComponent r, CodeSystemComparison res) {
423    CodeSystemFilterComponent cd = l.copy();
424    if (l.hasDescription() && !r.hasDescription()) {
425      cd.setDescription(null);
426    }
427    return cd;
428  }
429
430  private void mergeDesignations(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r) {
431    for (ConceptDefinitionDesignationComponent td : l.getDesignation()) {
432      if (hasDesignation(td, r.getDesignation())) {
433        cd.getDesignation().add(td);
434      }
435    }
436    for (ConceptDefinitionDesignationComponent td : r.getDesignation()) {
437      if (hasDesignation(td, l.getDesignation())) {
438        cd.getDesignation().add(td);
439      }
440    }
441  }
442
443  private boolean hasDesignation(ConceptDefinitionDesignationComponent td, List<ConceptDefinitionDesignationComponent> designation) {
444    for (ConceptDefinitionDesignationComponent t : designation) {
445      if (designationsMatch(td, t)) {
446        return true;
447      }
448    }
449    return false;
450  }
451
452  private boolean designationsMatch(ConceptDefinitionDesignationComponent l, ConceptDefinitionDesignationComponent r) {
453    if (l.hasUse() != r.hasUse()) {
454      return false;
455    }
456    if (l.hasLanguage() != r.hasLanguage()) {
457      return false;
458    }
459    if (l.hasValue() != r.hasValue()) {
460      return false;
461    }
462    if (l.hasUse()) {
463      if (l.getUse().equalsDeep(r.getUse())) {
464        return false;
465      }
466    }
467    if (l.hasLanguage()) {
468      if (l.getLanguageElement().equalsDeep(r.getLanguageElement())) {
469        return false;
470      }
471    }
472    if (l.hasValue()) {
473      if (l.getValueElement().equalsDeep(r.getValueElement())) {
474        return false;
475      }
476    }
477    return true;
478  }
479
480  private void mergeProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
481    List<ConceptPropertyComponent> matchR = new ArrayList<>();
482    for (ConceptPropertyComponent lp : l.getProperty()) {
483      ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
484      if (rp == null) {
485        cd.getProperty().add(lp);
486      } else {
487        matchR.add(rp);
488        cd.getProperty().add(lp);
489        if (lp.getValue().equalsDeep(rp.getValue())) {
490          cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));
491        }
492      }
493    }
494    for (ConceptPropertyComponent rp : r.getProperty()) {
495      if (!matchR.contains(rp)) {
496        cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));        
497      }
498    }
499  }
500
501  private void intersectProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
502    for (ConceptPropertyComponent lp : l.getProperty()) {
503      ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
504      if (rp != null) {
505        cd.getProperty().add(lp);
506      }
507    }
508  }
509
510  private ConceptPropertyComponent findRightProp(List<ConceptPropertyComponent> rightProperties, ConceptPropertyComponent lp, CodeSystemComparison res) {
511    for (ConceptPropertyComponent p : rightProperties) {
512      String rp = res.getPropMap().get(p.getCode());
513      if (rp != null && rp.equals(lp.getCode())) {
514        return p;
515      }
516    }
517    return null;
518  }
519
520  public XhtmlNode renderConcepts(CodeSystemComparison comparison, String id, String prefix) throws FHIRException, IOException {
521    // columns: code, display (left|right), properties (left|right)
522    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false);
523    TableModel model = gen.new TableModel(id, true);
524    model.setAlternating(true);
525    model.getTitles().add(gen.new Title(null, null, "Code", "The code for the concept", null, 100));
526    model.getTitles().add(gen.new Title(null, null, "Display", "The display for the concept", null, 200, 2));
527    for (PropertyComponent p : comparison.getUnion().getProperty()) {
528      model.getTitles().add(gen.new Title(null, null, p.getCode(), p.getDescription(), null, 100, 2));
529    }
530    model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
531    for (StructuralMatch<ConceptDefinitionComponent> t : comparison.getCombined().getChildren()) {
532      addRow(gen, model.getRows(), t, comparison);
533    }
534    return gen.generate(model, prefix, 0, null);
535  }
536
537  private void addRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ConceptDefinitionComponent> t, CodeSystemComparison comparison) {
538    Row r = gen.new Row();
539    rows.add(r);
540    r.getCells().add(gen.new Cell(null, null, t.either().getCode(), null, null));
541    if (t.hasLeft() && t.hasRight()) {
542      if (t.getLeft().hasDisplay() && t.getRight().hasDisplay()) {
543        if (t.getLeft().getDisplay().equals(t.getRight().getDisplay())) {
544          r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));        
545        } else {
546          r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));        
547          r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
548        }
549      } else if (t.getLeft().hasDisplay()) {
550        r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null));        
551        r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));        
552      } else if (t.getRight().hasDisplay()) {        
553        r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));        
554        r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null));        
555      } else {
556        r.getCells().add(missingCell(gen).span(2));
557      }
558      for (PropertyComponent p : comparison.getUnion().getProperty()) {
559        ConceptPropertyComponent lp = getProp(t.getLeft(), p, false, comparison);
560        ConceptPropertyComponent rp = getProp(t.getRight(), p, true, comparison);
561
562        if (lp != null && rp != null) {
563          if (lp.getValue().equals(rp.getValue())) {
564            r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));        
565          } else {
566            r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));        
567            r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));
568          }
569        } else if (lp != null) {
570          r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));        
571          r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));        
572        } else if (rp != null) {        
573          r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));        
574          r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));        
575        } else {
576          r.getCells().add(missingCell(gen).span(2));
577        }
578
579      }
580    } else if (t.hasLeft()) {
581      r.setColor(COLOR_NO_ROW_RIGHT);
582      r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
583      r.getCells().add(missingCell(gen));
584      for (PropertyComponent p : comparison.getUnion().getProperty()) {
585        r.getCells().add(propertyCell(gen, t.getLeft(), p, false, comparison));
586        r.getCells().add(missingCell(gen));
587      }
588    } else {
589      r.setColor(COLOR_NO_ROW_LEFT);
590      r.getCells().add(missingCell(gen));
591      r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
592      for (PropertyComponent p : comparison.getUnion().getProperty()) {
593        r.getCells().add(missingCell(gen));
594        r.getCells().add(propertyCell(gen, t.getLeft(), p, true, comparison));
595      }
596    }
597    r.getCells().add(cellForMessages(gen, t.getMessages()));
598  }
599
600  private Cell propertyCell(HierarchicalTableGenerator gen, ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
601    ConceptPropertyComponent cp = getProp(cd, p, right, comp);
602    if (cp == null) {
603      return missingCell(gen, right ? COLOR_NO_CELL_RIGHT : COLOR_NO_CELL_LEFT);
604    } else {
605      return gen.new Cell(null, null, cp.getValue().toString(), null, null);
606    }
607  }
608
609  public ConceptPropertyComponent getProp(ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
610    String c = p.getCode();
611    if (right) {
612      c = comp.getPropMap().get(c);
613    }
614    ConceptPropertyComponent cp = null;
615    if (cd != null) {
616      for (ConceptPropertyComponent t : cd.getProperty()) {
617        if (t.hasCode() && t.getCode().equals(c)) {
618          cp = t;
619        }
620      }
621    }
622    return cp;
623  }
624
625  @Override
626  protected String fhirType() {
627    return "CodeSystem";
628  }
629
630}