001package org.hl7.fhir.r5.renderers;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.HashMap;
006import java.util.List;
007
008import org.hl7.fhir.exceptions.DefinitionException;
009import org.hl7.fhir.exceptions.FHIRFormatError;
010import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
011import org.hl7.fhir.r5.model.ActorDefinition;
012import org.hl7.fhir.r5.model.CanonicalType;
013import org.hl7.fhir.r5.model.Coding;
014import org.hl7.fhir.r5.model.ElementDefinition;
015import org.hl7.fhir.r5.model.Extension;
016import org.hl7.fhir.r5.model.StructureDefinition;
017import org.hl7.fhir.r5.model.UsageContext;
018import org.hl7.fhir.r5.model.ValueSet;
019import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution;
020import org.hl7.fhir.r5.renderers.utils.RenderingContext;
021import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
022import org.hl7.fhir.r5.utils.ToolingExtensions;
023import org.hl7.fhir.r5.utils.UserDataNames;
024import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
025import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
026import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
027import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
028import org.hl7.fhir.utilities.xhtml.NodeType;
029import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
030import org.hl7.fhir.utilities.xhtml.XhtmlNode;
031import org.hl7.fhir.utilities.xhtml.XhtmlNodeList;
032
033@MarkedToMoveToAdjunctPackage
034public class ObligationsRenderer extends Renderer {
035  public static class ObligationDetail {
036    private List<String> codes = new ArrayList<>();
037    private List<String> elementIds = new ArrayList<>();
038    private List<CanonicalType> actors = new ArrayList<>();
039    private String doco;
040    private String docoShort;
041    private String filter;
042    private String filterDoco;
043    private List<UsageContext> usage = new ArrayList<>();
044    private boolean isUnchanged = false;
045    private boolean matched = false;
046    private boolean removed = false;
047    private String source;
048//    private ValueSet vs;
049    
050    private ObligationDetail compare;
051    private int count = 1;
052    
053    public ObligationDetail(Extension ext) {
054      for (Extension e: ext.getExtensionsByUrl("code")) {
055        codes.add(e.getValueStringType().toString());
056      }
057      for (Extension e: ext.getExtensionsByUrl("actor")) {
058        actors.add(e.getValueCanonicalType());
059      }
060      this.doco = ext.getExtensionString("documentation");
061      this.docoShort =  ext.getExtensionString("shortDoco");
062      this.filter =  ext.getExtensionString("filter");
063      this.filterDoco =  ext.getExtensionString("filterDocumentation");
064      if (this.filterDoco == null) {
065        this.filterDoco =  ext.getExtensionString("filter-desc");
066      }
067      for (Extension usage : ext.getExtensionsByUrl("usage")) {
068        this.usage.add(usage.getValueUsageContext());
069      }
070      for (Extension eid : ext.getExtensionsByUrl("elementId")) {
071        this.elementIds.add(eid.getValue().primitiveValue());
072      }
073      this.isUnchanged = ext.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS);
074      if (ext.hasExtension(ToolingExtensions.EXT_OBLIGATION_SOURCE, ToolingExtensions.EXT_OBLIGATION_SOURCE_SHORT)) {
075        this.source = ext.getExtensionString(ToolingExtensions.EXT_OBLIGATION_SOURCE, ToolingExtensions.EXT_OBLIGATION_SOURCE_SHORT);
076      } else if (ext.hasUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE)) {
077        this.source = ((StructureDefinition) ext.getUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE)).getVersionedUrl();        
078      }      
079    }
080    
081    private String getKey() {
082      // Todo: Consider extending this with content from usageContext if purpose isn't sufficiently differentiating
083      return String.join(",", codes) + Integer.toString(count);
084    }
085    
086    private void incrementCount() {
087      count++;
088    }
089    private void setCompare(ObligationDetail match) {
090      compare = match;
091      match.matched = true;
092    }
093    private boolean alreadyMatched() {
094      return matched;
095    }
096    public String getDoco(boolean full) {
097      return full ? doco : docoShort;
098    }
099    public String getCodes() {
100      return String.join(",", codes);
101    }
102    public List<String> getCodeList() {
103      return new ArrayList<String>(codes);
104    }
105    public boolean unchanged() {
106      if (!isUnchanged)
107        return false;
108      if (compare==null)
109        return true;
110      isUnchanged = true;
111      isUnchanged = isUnchanged && ((codes.isEmpty() && compare.codes.isEmpty()) || codes.equals(compare.codes));
112      isUnchanged = elementIds.equals(compare.elementIds);
113      isUnchanged = isUnchanged && ((actors.isEmpty() && compare.actors.isEmpty()) || actors.equals(compare.actors));
114      isUnchanged = isUnchanged && ((doco==null && compare.doco==null) || doco.equals(compare.doco));
115      isUnchanged = isUnchanged && ((docoShort==null && compare.docoShort==null) || docoShort.equals(compare.docoShort));
116      isUnchanged = isUnchanged && ((filter==null && compare.filter==null) || filter.equals(compare.filter));
117      isUnchanged = isUnchanged && ((filterDoco==null && compare.filterDoco==null) || filterDoco.equals(compare.filterDoco));
118      isUnchanged = isUnchanged && ((usage==null && compare.usage==null) || usage.equals(compare.usage));
119      return isUnchanged;
120    }
121    
122    public boolean hasFilter() {
123      return filter != null;
124    }
125
126    public boolean hasUsage() {
127      return !usage.isEmpty();
128    }
129
130    public String getFilterDesc() {
131      return filterDoco;
132    }
133
134    public String getFilter() {
135      return filter;
136    }
137
138    public List<UsageContext> getUsage() {
139      return usage;
140    }
141
142    public boolean hasActors() {
143      return !actors.isEmpty();
144    }
145
146    public boolean hasActor(String id) {
147      for (CanonicalType actor: actors) {
148        if (actor.getValue().equals(id))
149          return true;
150      }
151      return false;
152    }
153  }
154
155  private static String STYLE_UNCHANGED = "opacity: 0.5;";
156  private static String STYLE_REMOVED = STYLE_UNCHANGED + "text-decoration: line-through;";
157
158  private List<ObligationDetail> obligations = new ArrayList<>();
159  private String corePath;
160  private StructureDefinition profile;
161  private String path;
162  private RenderingContext context;
163  private IMarkdownProcessor md;
164  private CodeResolver cr;
165  private boolean canDoNoList;
166
167  public ObligationsRenderer(String corePath, StructureDefinition profile, String path, RenderingContext context, IMarkdownProcessor md, CodeResolver cr, boolean canDoNoList) {
168    super(context);
169    this.corePath = corePath;
170    this.profile = profile;
171    this.path = path;
172    this.context = context;
173    this.md = md;
174    this.cr = cr;
175    this.canDoNoList = canDoNoList;
176  }
177
178
179  public void seeObligations(ElementDefinition element, String id) {
180    seeObligations(element.getExtension(), null, false, id);
181  }
182
183  public void seeObligations(List<Extension> list) {
184    seeObligations(list, null, false, "$all");
185  }
186
187  public void seeRootObligations(String eid, List<Extension> list) {
188    seeRootObligations(eid, list, null, false, "$all");
189  }
190
191  public void seeObligations(List<Extension> list, List<Extension> compList, boolean compare, String id) {
192    HashMap<String, ObligationDetail> compBindings = new HashMap<String, ObligationDetail>();
193    if (compare && compList!=null) {
194      for (Extension ext : compList) {
195        ObligationDetail abr = obligationDetail(ext);
196        if (compBindings.containsKey(abr.getKey())) {
197          abr.incrementCount();
198        }
199        compBindings.put(abr.getKey(), abr);
200      }
201    }
202
203    for (Extension ext : list) {
204      ObligationDetail obd = obligationDetail(ext);
205      if ("$all".equals(id) || (obd.hasActor(id))) {
206        if (compare && compList!=null) {
207          ObligationDetail match = null;
208          do {
209            match = compBindings.get(obd.getKey());
210            if (obd.alreadyMatched())
211              obd.incrementCount();
212          } while (match!=null && obd.alreadyMatched());
213          if (match!=null)
214            obd.setCompare(match);
215          obligations.add(obd);
216          if (obd.compare!=null)
217            compBindings.remove(obd.compare.getKey());
218        } else {
219          obligations.add(obd);
220        }
221      }
222    }
223    for (ObligationDetail b: compBindings.values()) {
224      b.removed = true;
225      obligations.add(b);
226    }
227  }
228
229  public void seeRootObligations(String eid, List<Extension> list, List<Extension> compList, boolean compare, String id) {
230    HashMap<String, ObligationDetail> compBindings = new HashMap<String, ObligationDetail>();
231    if (compare && compList!=null) {
232      for (Extension ext : compList) {
233        if (forElement(eid, ext)) {
234          ObligationDetail abr = obligationDetail(ext);
235          if (compBindings.containsKey(abr.getKey())) {
236            abr.incrementCount();
237          }
238          compBindings.put(abr.getKey(), abr);
239        }
240      }
241    }
242
243    for (Extension ext : list) {
244      if (forElement(eid, ext)) {
245        ObligationDetail obd = obligationDetail(ext);
246        obd.elementIds.clear();
247        if ("$all".equals(id) || (obd.hasActor(id))) {
248          if (compare && compList!=null) {
249            ObligationDetail match = null;
250            do {
251              match = compBindings.get(obd.getKey());
252              if (obd.alreadyMatched())
253                obd.incrementCount();
254            } while (match!=null && obd.alreadyMatched());
255            if (match!=null)
256              obd.setCompare(match);
257            obligations.add(obd);
258            if (obd.compare!=null)
259              compBindings.remove(obd.compare.getKey());
260          } else {
261            obligations.add(obd);
262          }
263        }
264      }
265    }
266    for (ObligationDetail b: compBindings.values()) {
267      b.removed = true;
268      obligations.add(b);
269    }
270  }
271
272
273  private boolean forElement(String eid, Extension ext) {
274
275    for (Extension exid : ext.getExtensionsByUrl("elementId")) {
276      if (eid.equals(exid.getValue().primitiveValue())) {
277        return true;
278      }
279    } 
280    return false;
281  }
282
283
284  protected ObligationDetail obligationDetail(Extension ext) {
285    ObligationDetail abr = new ObligationDetail(ext);
286    return abr;
287  }
288
289  public String render(RenderingStatus status, ResourceWrapper res, String defPath, String anchorPrefix, List<ElementDefinition> inScopeElements) throws IOException {
290    if (obligations.isEmpty()) {
291      return "";
292    } else {
293      XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table");
294      tbl.attribute("class", "grid");
295      renderTable(status, res, tbl.getChildNodes(), true, defPath, anchorPrefix, inScopeElements);
296      return new XhtmlComposer(false).compose(tbl);
297    }
298  }
299
300  public void renderTable(RenderingStatus status, ResourceWrapper res, HierarchicalTableGenerator gen, Cell c, List<ElementDefinition> inScopeElements) throws FHIRFormatError, DefinitionException, IOException {
301    if (obligations.isEmpty()) {
302      return;
303    } else {
304      Piece piece = gen.new Piece("obligation", "table").setClass("grid");
305      c.getPieces().add(piece);
306      renderTable(status, res, piece.getChildren(), false, gen.getDefPath(), gen.getUniqueLocalPrefix(), inScopeElements);
307    }
308  }
309
310  public void renderList(HierarchicalTableGenerator gen, Cell c) throws FHIRFormatError, DefinitionException, IOException {
311    if (obligations.size() > 0) {
312      Piece p = gen.new Piece(null);
313      c.addPiece(p);
314      if (obligations.size() == 1 && canDoNoList) {
315        renderObligationLI(p.getChildren(), obligations.get(0));
316      } else {
317        XhtmlNode ul = p.getChildren().ul();
318        for (ObligationDetail ob : obligations) {
319          renderObligationLI(ul.li().getChildNodes(), ob);
320        }
321      }
322    }
323  }
324
325  private void renderObligationLI(XhtmlNodeList children, ObligationDetail ob) throws IOException {
326    renderCodes(children, ob.getCodeList());
327    if (ob.hasFilter() || ob.hasUsage() || !ob.elementIds.isEmpty()) {
328      children.tx(" (");
329      boolean ffirst = !ob.hasFilter();
330      boolean firstEid = true;
331
332      for (String eid: ob.elementIds) {
333        if (firstEid) {
334          children.span().i().tx("Elements: ");
335          firstEid = false;
336        } else
337          children.tx(", ");
338        String trimmedElement = eid.substring(eid.indexOf(".")+ 1);
339        children.tx(trimmedElement);
340      }
341      if (ob.hasFilter()) {
342        children.span(null, ob.getFilterDesc()).code().tx(ob.getFilter());
343      }
344      for (UsageContext uc : ob.getUsage()) {
345        if (ffirst) ffirst = false; else children.tx(",");
346        if (!uc.getCode().is("http://terminology.hl7.org/CodeSystem/usage-context-type", "jurisdiction")) {
347          children.tx(displayForUsage(uc.getCode()));
348          children.tx("=");
349        }
350        CodeResolution ccr = this.cr.resolveCode(uc.getValueCodeableConcept());
351        children.ah(context.prefixLocalHref(ccr.getLink()), ccr.getHint()).tx(ccr.getDisplay());
352      }
353      children.tx(")");
354    }
355    if (ob.source != null && !ob.source.equals(profile.getVersionedUrl())) {
356      children.tx(" ");
357      StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, ob.source);
358      String link = sd != null ? sd.getWebPath() : ob.source;
359      String title = context.formatPhrase(RenderingContext.OBLIGATION_SOURCE, sd == null ? ob.source : sd.present()); 
360      children.ah(link, title).attribute("data-no-external", "true").img("external.png", "source-link");
361    }
362    // usage
363    // filter
364    // process 
365  }
366
367
368  public void renderTable(RenderingStatus status, ResourceWrapper res, List<XhtmlNode> children, boolean fullDoco, String defPath, String anchorPrefix, List<ElementDefinition> inScopeElements) throws FHIRFormatError, DefinitionException, IOException {
369    boolean hasDoco = false;
370    boolean hasUsage = false;
371    boolean hasActor = false;
372    boolean hasFilter = false;
373    boolean hasElementId = false;
374    boolean hasSource = false;
375    for (ObligationDetail ob : obligations) {
376      hasActor = hasActor || !ob.actors.isEmpty()  || (ob.compare!=null && !ob.compare.actors.isEmpty());
377      hasDoco = hasDoco || ob.getDoco(fullDoco)!=null  || (ob.compare!=null && ob.compare.getDoco(fullDoco)!=null);
378      hasUsage = hasUsage || !ob.usage.isEmpty() || (ob.compare!=null && !ob.compare.usage.isEmpty());
379      hasFilter = hasFilter || ob.filter != null || (ob.compare!=null && ob.compare.filter!=null);
380      hasElementId = hasElementId || !ob.elementIds.isEmpty()  || (ob.compare!=null && !ob.compare.elementIds.isEmpty());
381      hasSource = hasSource || ob.source != null || (ob.compare!=null && ob.compare.source!=null);
382    }
383
384    List<String> inScopePaths = new ArrayList<>();
385    for (ElementDefinition e: inScopeElements) {
386      inScopePaths.add(e.getPath());
387    }
388
389    XhtmlNode tr = new XhtmlNode(NodeType.Element, "tr");
390    children.add(tr);
391    tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_OBLIG));
392    if (hasActor) {
393      tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.OBLIG_ACT));
394    }
395    if (hasElementId) {
396      tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.OBLIG_ELE));
397    }
398    if (hasUsage) {
399      tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_USAGE));
400    }
401    if (hasDoco) {
402      tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION));
403    }
404    if (hasFilter) {
405      tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER));
406    }
407    if (hasFilter) {
408      tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_SOURCE));
409    }
410    for (ObligationDetail ob : obligations) {
411      tr =  new XhtmlNode(NodeType.Element, "tr");
412      if (ob.unchanged()) {
413        tr.style(STYLE_REMOVED);
414      } else if (ob.removed) {
415        tr.style(STYLE_REMOVED);
416      }
417      children.add(tr);
418
419      XhtmlNode code = tr.td().style("font-size: 11px");
420      if (ob.compare!=null && ob.getCodes().equals(ob.compare.getCodes()))
421        code.style("font-color: darkgray");
422        renderCodes(code.getChildNodes(), ob.getCodeList());
423      if (ob.compare!=null && !ob.compare.getCodeList().isEmpty() && !ob.getCodes().equals(ob.compare.getCodes())) {
424        code.br();
425        code = code.span(STYLE_UNCHANGED, null);
426        renderCodes(code.getChildNodes(), ob.compare.getCodeList());
427      }
428
429      XhtmlNode actorId = tr.td().style("font-size: 11px");
430      if (!ob.actors.isEmpty() || ob.compare.actors.isEmpty()) {
431        boolean firstActor = true;
432        for (CanonicalType anActor : ob.actors) {
433          ActorDefinition ad = context.getContext().fetchResource(ActorDefinition.class, anActor.getCanonical());
434          boolean existingActor = ob.compare != null && ob.compare.actors.contains(anActor);
435
436          if (!firstActor) {
437            actorId.br();
438            firstActor = false;
439          }
440
441          if (!existingActor)
442            actorId.style(STYLE_UNCHANGED);
443          if (ad == null) {
444            actorId.addText(anActor.getCanonical());
445          } else {
446            actorId.ah(ad.getWebPath()).tx(ad.present());
447          }
448        }
449
450        if (ob.compare != null) {
451          for (CanonicalType compActor : ob.compare.actors) {
452            if (!ob.actors.contains(compActor)) {
453              ActorDefinition compAd = context.getContext().fetchResource(ActorDefinition.class, compActor.toString());
454              if (!firstActor) {
455                actorId.br();
456                firstActor = true;
457              }
458              actorId = actorId.span(STYLE_REMOVED, null);
459              if (compAd == null) {
460                actorId.ah(context.prefixLocalHref(compActor.toString()), compActor.toString()).tx(compActor.toString());
461              } else if (compAd.hasWebPath()) {
462                actorId.ah(context.prefixLocalHref(compAd.getWebPath()), compActor.toString()).tx(compAd.present());
463              } else {
464                actorId.span(null, compActor.toString()).tx(compAd.present());
465              }
466            }
467          }
468        }
469      }
470
471
472      if (hasElementId) {
473        XhtmlNode elementIds = tr.td().style("font-size: 11px");
474        if (ob.compare!=null && ob.elementIds.equals(ob.compare.elementIds))
475          elementIds.style(STYLE_UNCHANGED);
476        for (String eid : ob.elementIds) {
477          elementIds.sep(", ");
478          ElementDefinition ed = profile.getSnapshot().getElementById(eid);
479          if (ed != null) {
480            boolean inScope = inScopePaths.contains(ed.getPath());
481            String name = eid.substring(eid.indexOf(".") + 1);
482            if (ed != null && inScope) {
483              String link = defPath + "#" + anchorPrefix + eid;
484              elementIds.ah(context.prefixLocalHref(link)).tx(name);
485            } else {
486              elementIds.code().tx(name);
487            }
488          }
489        }
490
491        if (ob.compare!=null && !ob.compare.elementIds.isEmpty()) {
492          for (String eid : ob.compare.elementIds) {
493            if (!ob.elementIds.contains(eid)) {
494              elementIds.sep(", ");
495              elementIds.span(STYLE_REMOVED, null).code().tx(eid);
496            }
497          }
498        }
499      }
500      if (hasUsage) {
501        if (ob.usage != null) {
502          boolean first = true;
503          XhtmlNode td = tr.td();
504          for (UsageContext u : ob.usage) {
505            if (first) first = false; else td.tx(", ");
506            new DataRenderer(context).renderDataType(status, td, wrapWC(res, u));
507          }
508        } else {
509          tr.td();          
510        }
511      }
512      if (hasDoco) {
513        if (ob.doco != null) {
514          String d = fullDoco ? md.processMarkdown("Obligation.documentation", ob.doco) : ob.docoShort;
515          String oldD = ob.compare==null ? null : fullDoco ? md.processMarkdown("Binding.description.compare", ob.compare.doco) : ob.compare.docoShort;
516          tr.td().style("font-size: 11px").innerHTML(compareHtml(d, oldD));
517        } else {
518          tr.td().style("font-size: 11px");
519        }
520      }
521
522      if (hasFilter) {
523        if (ob.filter != null) {
524          String d = "<code>"+ob.filter+"</code>" + (fullDoco ? md.processMarkdown("Binding.description", ob.filterDoco) : "");
525          String oldD = ob.compare==null ? null : "<code>"+ob.compare.filter+"</code>" + (fullDoco ? md.processMarkdown("Binding.description", ob.compare.filterDoco) : "");
526          tr.td().style("font-size: 11px").innerHTML(compareHtml(d, oldD));
527        } else {
528          tr.td().style("font-size: 11px");
529        }
530      }
531      if (hasSource) {
532        if (ob.source != null && !ob.source.equals(profile.getVersionedUrl())) {
533          StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, ob.source);
534          var td = tr.td().style("font-size: 11px");
535          td.tx("from ");
536          if (sd != null) {
537            td.ah(sd.getWebPath()).tx(sd.present());
538          } else {
539            td.code().tx(ob.source);            
540          }
541        } else {
542          tr.td().style("font-size: 11px");
543        }
544      }
545      
546    }
547  }
548
549  private XhtmlNode compareString(XhtmlNode node, String newS, String oldS) {
550    if (oldS==null)
551      return node.tx(newS);
552    if (newS.equals(oldS))
553      return node.style(STYLE_UNCHANGED).tx(newS);
554    node.tx(newS);
555    node.br();
556    return node.span(STYLE_REMOVED,null).tx(oldS);
557  }
558
559  private String compareHtml(String newS, String oldS) {
560    if (oldS==null)
561      return newS;
562    if (newS.equals(oldS))
563      return "<span style=\"" + STYLE_UNCHANGED + "\">" + newS + "</span>";
564    return newS + "<br/><span style=\"" + STYLE_REMOVED + "\">" + oldS + "</span>";
565  }
566
567  private void renderCodes(XhtmlNodeList children, List<String> codes) {
568
569    if (!codes.isEmpty()) {
570      boolean first = true;
571      for (String code : codes) {
572        if (first) first = false; else children.tx(" & ");
573        int i = code.indexOf(":");
574        if (i > -1) {
575          String c = code.substring(0, i);
576          code = code.substring(i+1);
577          children.b().tx(c.toUpperCase());
578          children.tx(":");
579        }
580        CodeResolution cr = this.cr.resolveCode("http://hl7.org/fhir/CodeSystem/obligation", code);
581        if (cr == null) {
582          cr = this.cr.resolveCode("http://hl7.org/fhir/tools/CodeSystem/obligation", code);
583        }
584        code = code.replace("will-", "").replace("can-", "");
585        if (cr.getLink() != null) {
586          children.ah(context.prefixLocalHref(cr.getLink()), cr.getHint()).tx(code);
587        } else {
588          children.span(null, cr.getHint()).tx(code);
589        }
590      }
591    } else {
592      children.span(null, "No Obligation Code?").tx("??");
593    }
594  }
595
596  public boolean hasObligations() {
597    return !obligations.isEmpty();
598  }
599
600  private String displayForUsage(Coding c) {
601    if (c.hasDisplay()) {
602      return c.getDisplay();
603    }
604    if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) {
605      return c.getCode();
606    }
607    return c.getCode();
608  }
609
610}