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