001package org.hl7.fhir.r5.terminologies;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033
034import java.util.ArrayList;
035import java.util.Calendar;
036import java.util.Collections;
037import java.util.Comparator;
038import java.util.HashSet;
039import java.util.List;
040import java.util.Set;
041
042import org.hl7.fhir.exceptions.FHIRException;
043import org.hl7.fhir.exceptions.FHIRFormatError;
044import org.hl7.fhir.r5.context.IWorkerContext;
045import org.hl7.fhir.r5.model.BooleanType;
046import org.hl7.fhir.r5.model.CanonicalResource;
047import org.hl7.fhir.r5.model.CanonicalType;
048import org.hl7.fhir.r5.model.CodeSystem;
049import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
050import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
051import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
052import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
053import org.hl7.fhir.r5.model.CodeSystem.PropertyType;
054import org.hl7.fhir.r5.model.CodeType;
055import org.hl7.fhir.r5.model.Coding;
056import org.hl7.fhir.r5.model.DataType;
057import org.hl7.fhir.r5.model.DateTimeType;
058import org.hl7.fhir.r5.model.DecimalType;
059import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
060import org.hl7.fhir.r5.model.Extension;
061import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter;
062import org.hl7.fhir.r5.model.Identifier;
063import org.hl7.fhir.r5.model.IntegerType;
064import org.hl7.fhir.r5.model.Meta;
065import org.hl7.fhir.r5.model.StringType;
066import org.hl7.fhir.r5.model.UriType;
067import org.hl7.fhir.r5.utils.ToolingExtensions;
068import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
069import org.hl7.fhir.utilities.MarkDownProcessor;
070import org.hl7.fhir.utilities.StandardsStatus;
071import org.hl7.fhir.utilities.Utilities;
072
073public class CodeSystemUtilities {
074
075  public static class SystemReference {
076    private String link;
077    private String text;
078    private boolean local;
079    
080    public SystemReference(String text, String link) {
081      super();
082      this.link = link;
083      this.text = text;
084    }
085    public SystemReference(String text, String link, boolean local) {
086      super();
087      this.link = link;
088      this.text = text;
089      this.local = local;
090    }
091    
092    public String getLink() {
093      return link;
094    }
095    public String getText() {
096      return text;
097    }
098    public boolean isLocal() {
099      return local;
100    }
101    
102  }
103
104  public static class ConceptDefinitionComponentSorter implements Comparator<ConceptDefinitionComponent> {
105
106    @Override
107    public int compare(ConceptDefinitionComponent o1, ConceptDefinitionComponent o2) {
108      return o1.getCode().compareToIgnoreCase(o2.getCode());
109    }
110
111  }
112
113  public static final String USER_DATA_CROSS_LINK = "cs.utils.cross.link";
114
115  public static class CodeSystemNavigator {
116
117    private CodeSystem cs;
118    private boolean restructure;
119    private Set<String> processed = new HashSet<>();
120
121    public CodeSystemNavigator(CodeSystem cs) {
122      this.cs = cs;
123      restructure = hasExtraRelationships(cs.getConcept());
124    }
125
126    public boolean isRestructure() {
127      return restructure;
128    }
129
130    private boolean hasExtraRelationships(List<ConceptDefinitionComponent> concept) {
131      for (ConceptDefinitionComponent cd : concept) {
132        if (!getSubsumedBy(cd).isEmpty()) {
133          return true;
134        }
135        for (ConceptDefinitionComponent cdc : cd.getConcept()) {
136          if (hasExtraRelationships(cdc.getConcept())) {
137            return true;
138          }
139        }
140      }
141      return false;
142    }
143
144    public List<ConceptDefinitionComponent> getConcepts(ConceptDefinitionComponent context) {
145      if (context == null) {
146        if (restructure) {
147          List<ConceptDefinitionComponent> res = new ArrayList<>();
148          for (ConceptDefinitionComponent cd : cs.getConcept()) {
149            if (getSubsumedBy(cd).isEmpty()) {
150              res.add(cd);
151              processed.add(cd.getCode());
152            }
153          }
154          return res;
155        } else {
156          return cs.getConcept();
157        }
158      } else {
159        if (restructure) {
160          List<ConceptDefinitionComponent> res = new ArrayList<>();
161          for (ConceptDefinitionComponent cd : context.getConcept()) {
162            res.add(cd);
163            processed.add(cd.getCode());
164          }
165          for (ConceptDefinitionComponent cd : cs.getConcept()) {
166            if (getSubsumedBy(cd).contains(context.getCode()) && !processed.contains(cd.getCode())) {
167              res.add(cd);
168              processed.add(cd.getCode());
169            }
170          }
171          return res;
172        } else {
173          return context.getConcept();
174        }
175      }
176    }
177
178    private List<String> getSubsumedBy(ConceptDefinitionComponent cd) {
179      List<String> codes = new ArrayList<>();
180      for (ConceptPropertyComponent cp : cd.getProperty()) {
181        if ("subsumedBy".equals(cp.getCode())) {
182          codes.add(cp.getValue().primitiveValue());
183        }
184      }
185      return codes;
186    }
187
188    public List<ConceptDefinitionComponent> getOtherChildren(ConceptDefinitionComponent context) {
189      List<ConceptDefinitionComponent> res = new ArrayList<>();
190      for (ConceptDefinitionComponent cd : cs.getConcept()) {
191        if (getSubsumedBy(cd).contains(context.getCode()) && processed.contains(cd.getCode())) {
192          res.add(cd);
193        }
194      }
195      return res;
196    }
197  }
198
199
200  public static boolean isNotSelectable(CodeSystem cs, ConceptDefinitionComponent def) {
201    for (ConceptPropertyComponent p : def.getProperty()) {
202      if ("notSelectable".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
203        return ((BooleanType) p.getValue()).getValue();
204    }
205    return false;
206  }
207
208  public static void setNotSelectable(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError {
209    defineNotSelectableProperty(cs);
210    ConceptPropertyComponent p = getProperty(concept, "notSelectable");
211    if (p != null)
212      p.setValue(new BooleanType(true));
213    else
214      concept.addProperty().setCode("notSelectable").setValue(new BooleanType(true));    
215  }
216
217  public static void setProperty(CodeSystem cs, ConceptDefinitionComponent concept, String code, DataType value) throws FHIRFormatError {
218    defineProperty(cs, code, propertyTypeForValue(value));
219    ConceptPropertyComponent p = getProperty(concept,  code);
220    if (p != null)
221      p.setValue(value);
222    else
223      concept.addProperty().setCode(code).setValue(value);    
224  }
225  
226
227  private static PropertyType propertyTypeForValue(DataType value) {
228    if (value instanceof BooleanType) {
229      return PropertyType.BOOLEAN;
230    }
231    if (value instanceof CodeType) {
232      return PropertyType.CODE;
233    }
234    if (value instanceof Coding) {
235      return PropertyType.CODING;
236    }
237    if (value instanceof DateTimeType) {
238      return PropertyType.DATETIME;
239    }
240    if (value instanceof DecimalType) {
241      return PropertyType.DECIMAL;
242    }
243    if (value instanceof IntegerType) {
244      return PropertyType.INTEGER;
245    }
246    if (value instanceof StringType) {
247      return PropertyType.STRING;
248    }
249    throw new Error("Unknown property type "+value.getClass().getName());
250  }
251
252  private static String defineProperty(CodeSystem cs, String code, PropertyType pt) {
253    String url = "http://hl7.org/fhir/concept-properties#"+code;
254    for (PropertyComponent p : cs.getProperty()) {
255      if (p.getCode().equals(code)) {
256        if (!p.getUri().equals(url)) {
257          throw new Error("URI mismatch for code "+code+" url = "+p.getUri()+" vs "+url);
258        }
259        if (!p.getType().equals(pt)) {
260          throw new Error("Type mismatch for code "+code+" type = "+p.getType()+" vs "+pt);
261        }
262        return code;
263      }
264    }
265    cs.addProperty().setCode(code).setUri(url).setType(pt).setUri(url);
266    return code;
267  }
268
269  public static void defineNotSelectableProperty(CodeSystem cs) {
270    defineCodeSystemProperty(cs, "notSelectable", "Indicates that the code is abstract - only intended to be used as a selector for other concepts", PropertyType.BOOLEAN);
271  }
272
273
274  public enum ConceptStatus {
275    Active, Experimental, Deprecated, Retired;
276
277    public String toCode() {
278      switch (this) {
279      case Active: return "active";
280      case Experimental: return "experimental";
281      case Deprecated: return "deprecated";
282      case Retired: return "retired";
283      default: return null;
284      }
285    }
286  }
287
288  public static void setStatus(CodeSystem cs, ConceptDefinitionComponent concept, ConceptStatus status) throws FHIRFormatError {
289    defineStatusProperty(cs);
290    ConceptPropertyComponent p = getProperty(concept, "status");
291    if (p != null)
292      p.setValue(new CodeType(status.toCode()));
293    else
294      concept.addProperty().setCode("status").setValue(new CodeType(status.toCode()));    
295  }
296
297  public static void defineStatusProperty(CodeSystem cs) {
298    defineCodeSystemProperty(cs, "status", "A property that indicates the status of the concept. One of active, experimental, deprecated, retired", PropertyType.CODE);
299  }
300
301  private static void defineDeprecatedProperty(CodeSystem cs) {
302    defineCodeSystemProperty(cs, "deprecationDate", "The date at which a concept was deprecated. Concepts that are deprecated but not inactive can still be used, but their use is discouraged", PropertyType.DATETIME);
303  }
304
305  public static void defineParentProperty(CodeSystem cs) {
306    defineCodeSystemProperty(cs, "parent", "The concept identified in this property is a parent of the concept on which it is a property. The property type will be 'code'. The meaning of parent/child relationships is defined by the hierarchyMeaning attribute", PropertyType.CODE);
307  }
308
309  public static void defineChildProperty(CodeSystem cs) {
310    defineCodeSystemProperty(cs, "child", "The concept identified in this property is a child of the concept on which it is a property. The property type will be 'code'. The meaning of parent/child relationships is defined by the hierarchyMeaning attribute", PropertyType.CODE);
311  }
312
313  public static boolean isDeprecated(CodeSystem cs, ConceptDefinitionComponent def, boolean ignoreStatus)  {
314    try {
315      for (ConceptPropertyComponent p : def.getProperty()) {
316        if (!ignoreStatus) {
317          if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode()))
318            return true;
319        }
320        // this, though status should also be set
321        if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 
322          return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance()));
323        // legacy  
324        if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
325          return ((BooleanType) p.getValue()).getValue();
326      }
327      StandardsStatus ss = ToolingExtensions.getStandardsStatus(def);
328      if (ss == StandardsStatus.DEPRECATED) {
329        return true;
330      }
331      return false;
332    } catch (FHIRException e) {
333      return false;
334    }
335  }
336
337  public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def, boolean ignoreStatus)  {
338    try {
339      for (ConceptPropertyComponent p : def.getProperty()) {
340        if (!ignoreStatus) {
341          if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "inactive".equals(p.getValueCodeType().getCode()))
342            return true;
343        }
344        // legacy  
345        if ("inactive".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
346          return ((BooleanType) p.getValue()).getValue();
347      }
348      return false;
349    } catch (FHIRException e) {
350      return false;
351    }
352  }
353
354  public static void setDeprecated(CodeSystem cs, ConceptDefinitionComponent concept, DateTimeType date) throws FHIRFormatError {
355    setStatus(cs, concept, ConceptStatus.Deprecated);
356    defineDeprecatedProperty(cs);
357    concept.addProperty().setCode("deprecationDate").setValue(date);    
358  }
359
360
361  public static void setDeprecated(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError {
362    setStatus(cs, concept, ConceptStatus.Deprecated);
363  }
364  
365  public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def) throws FHIRException {
366    StandardsStatus ss = ToolingExtensions.getStandardsStatus(def);
367    if (ss == StandardsStatus.DEPRECATED || ss == StandardsStatus.WITHDRAWN) {
368      return true;
369    }
370    for (ConceptPropertyComponent p : def.getProperty()) {
371      if ("status".equals(p.getCode()) && p.hasValueStringType()) {
372        return "inactive".equals(p.getValueStringType().primitiveValue()) || "retired".equals(p.getValueStringType().primitiveValue()) || "deprecated".equals(p.getValueStringType().primitiveValue());
373      }
374      if ("inactive".equals(p.getCode()) && p.hasValueBooleanType()) {
375        return p.getValueBooleanType().getValue();
376      }
377      if ("inactive".equals(p.getCode()) && p.hasValueCodeType()) {
378        String code = p.getValueCodeType().primitiveValue();
379        return "true".equals(code);
380      }
381    }
382    return false;
383  }
384  
385  public static boolean isInactive(CodeSystem cs, String code) throws FHIRException {
386    ConceptDefinitionComponent def = findCode(cs.getConcept(), code);
387    if (def == null)
388      return true;
389    return isInactive(cs, def);
390  }
391
392  public static void defineCodeSystemProperty(CodeSystem cs, String code, String description, PropertyType type) {
393    for (PropertyComponent p : cs.getProperty()) {
394      if (p.getCode().equals(code))
395        return;
396    }
397    cs.addProperty().setCode(code).setDescription(description).setType(type).setUri("http://hl7.org/fhir/concept-properties#"+code);
398  }
399
400  public static String getCodeDefinition(CodeSystem cs, String code) {
401    return getCodeDefinition(cs.getConcept(), code);
402  }
403
404  private static String getCodeDefinition(List<ConceptDefinitionComponent> list, String code) {
405    for (ConceptDefinitionComponent c : list) {
406      if (c.hasCode() &&  c.getCode().equals(code))
407        return c.getDefinition();
408      String s = getCodeDefinition(c.getConcept(), code);
409      if (s != null)
410        return s;
411    }
412    return null;
413  }
414
415  public static CodeSystem makeShareable(CodeSystem cs) {
416    if (!cs.hasExperimental()) {
417      cs.setExperimental(false);
418    }
419
420    if (!cs.hasMeta())
421      cs.setMeta(new Meta());
422    for (UriType t : cs.getMeta().getProfile()) 
423      if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue()))
424        return cs;
425    cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
426    return cs;
427  }
428
429  public static boolean checkMakeShareable(CodeSystem cs) {
430    boolean changed = false;
431    if (!cs.hasExperimental()) {
432      cs.setExperimental(false);
433      changed = true;
434    }
435
436    if (!cs.hasMeta())
437      cs.setMeta(new Meta());
438    for (UriType t : cs.getMeta().getProfile()) 
439      if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue()))
440        return changed;
441    cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
442    return true;
443  }
444
445  public static void setOID(CodeSystem cs, String oid) {
446    if (!oid.startsWith("urn:oid:"))
447       oid = "urn:oid:" + oid;
448    if (!cs.hasIdentifier())
449      cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid));
450    else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
451      cs.getIdentifierFirstRep().setValue(oid);
452    else
453      throw new Error("unable to set OID on code system");
454    
455  }
456
457  public static boolean hasOID(CanonicalResource cs) {
458    return getOID(cs) != null;
459  }
460
461  public static String getOID(CanonicalResource cs) {
462    if (cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
463        return cs.getIdentifierFirstRep().getValue().substring(8);
464    return null;
465  }
466
467  public static ConceptDefinitionComponent findCode(List<ConceptDefinitionComponent> list, String code) {
468    for (ConceptDefinitionComponent c : list) {
469      if (c.getCode().equals(code))
470        return c;
471      ConceptDefinitionComponent s = findCode(c.getConcept(), code);
472      if (s != null)
473        return s;
474    }
475    return null;
476  }
477
478  public static ConceptDefinitionComponent findCodeOrAltCode(List<ConceptDefinitionComponent> list, String code, String use) {
479    for (ConceptDefinitionComponent c : list) {
480      if (c.getCode().equals(code))
481        return c;
482      for (ConceptPropertyComponent p : c.getProperty()) {
483        if ("alternateCode".equals(p.getCode()) && (use == null || hasUse(p, use)) && p.hasValue() && p.getValue().isPrimitive() && code.equals(p.getValue().primitiveValue())) {
484          return c;
485        }
486      }
487      ConceptDefinitionComponent s = findCodeOrAltCode(c.getConcept(), code, use);
488      if (s != null)
489        return s;
490    }
491    return null;
492  }
493
494  private static boolean hasUse(ConceptPropertyComponent p, String use) {
495    for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_USE)) {
496      if (ext.hasValueCoding() && use.equals(ext.getValueCoding().getCode())) {
497        return true;
498      }
499    }
500    return false;
501  }
502
503  public static void markStatus(CodeSystem cs, String wg, StandardsStatus status, String pckage, String fmm, String normativeVersion) throws FHIRException {
504    if (wg != null) {
505      if (!ToolingExtensions.hasExtension(cs, ToolingExtensions.EXT_WORKGROUP) || 
506          (Utilities.existsInList(ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) {
507        ToolingExtensions.setCodeExtension(cs, ToolingExtensions.EXT_WORKGROUP, wg);
508      }
509    }
510    if (status != null) {
511      StandardsStatus ss = ToolingExtensions.getStandardsStatus(cs);
512      if (ss == null || ss.isLowerThan(status)) 
513        ToolingExtensions.setStandardsStatus(cs, status, normativeVersion);
514      if (pckage != null) {
515        if (!cs.hasUserData("ballot.package"))
516          cs.setUserData("ballot.package", pckage);
517        else if (!pckage.equals(cs.getUserString("ballot.package")))
518          if (!"infrastructure".equals(cs.getUserString("ballot.package")))
519            System.out.println("Code System "+cs.getUrl()+": ownership clash "+pckage+" vs "+cs.getUserString("ballot.package"));
520      }
521      if (status == StandardsStatus.NORMATIVE) {
522        cs.setExperimental(false);
523        cs.setStatus(PublicationStatus.ACTIVE);
524      }
525    }
526    if (fmm != null) {
527      String sfmm = ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_FMM_LEVEL);
528      if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 
529        ToolingExtensions.setIntegerExtension(cs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm));
530      }
531      if (Integer.parseInt(fmm) <= 1) {
532        cs.setExperimental(true);
533      }
534    }
535  }
536
537 
538  public static DataType readProperty(ConceptDefinitionComponent concept, String code) {
539    for (ConceptPropertyComponent p : concept.getProperty())
540      if (p.getCode().equals(code))
541        return p.getValue(); 
542    return null;
543  }
544
545  public static ConceptPropertyComponent getProperty(ConceptDefinitionComponent concept, String code) {
546    for (ConceptPropertyComponent p : concept.getProperty())
547      if (p.getCode().equals(code))
548        return p; 
549    return null;
550  }
551  
552  public static List<ConceptPropertyComponent> getPropertyValues(ConceptDefinitionComponent concept, String code) {
553    List<ConceptPropertyComponent> res = new ArrayList<>();
554    for (ConceptPropertyComponent p : concept.getProperty()) {
555      if (p.getCode().equals(code)) {
556        res.add(p); 
557      }
558    }
559    return res;
560  }
561
562
563  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
564  // returns additional parents not in the heirarchy
565  public static List<String> getOtherChildren(CodeSystem cs, ConceptDefinitionComponent c) {
566    List<String> res = new ArrayList<String>();
567    for (ConceptPropertyComponent p : c.getProperty()) {
568      if ("parent".equals(p.getCode())) {
569        res.add(p.getValue().primitiveValue());
570      }
571    }
572    return res;
573  }
574
575  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
576  public static void addOtherChild(CodeSystem cs, ConceptDefinitionComponent owner, String code) {
577    defineChildProperty(cs);
578    owner.addProperty().setCode("child").setValue(new CodeType(code));
579  }
580
581  public static boolean hasProperty(ConceptDefinitionComponent c, String code) {
582    for (ConceptPropertyComponent cp : c.getProperty()) {
583      if (code.equals(cp.getCode())) {
584        return true;
585      }
586    }
587    return false;
588  }
589
590  public static boolean hasCode(CodeSystem cs, String code) {
591    for (ConceptDefinitionComponent cc : cs.getConcept()) {
592      if (hasCode(cc, code)) {
593        return true;
594      }
595    }
596    return false;
597  }
598
599  private static boolean hasCode(ConceptDefinitionComponent cc, String code) {
600    if (code.equals(cc.getCode())) {
601      return true;
602    }
603    for (ConceptDefinitionComponent c : cc.getConcept()) {
604      if (hasCode(c, code)) {
605        return true;
606      }
607    }
608    return false;
609  }
610
611  public static ConceptDefinitionComponent getCode(CodeSystem cs, String code) {
612    if (code == null) {
613      return null;
614    }
615    for (ConceptDefinitionComponent cc : cs.getConcept()) {
616      ConceptDefinitionComponent cd = getCode(cc, code);
617      if (cd != null) {
618        return cd;
619      }
620    }
621    return null;
622  }
623
624  private static ConceptDefinitionComponent getCode(ConceptDefinitionComponent cc, String code) {
625    if (code.equals(cc.getCode())) {
626      return cc;
627    }
628    for (ConceptDefinitionComponent c : cc.getConcept()) {
629      ConceptDefinitionComponent cd = getCode(c, code);
630      if (cd != null) {
631        return cd;
632      }
633    }
634    return null;
635  }
636
637  public static void crossLinkCodeSystem(CodeSystem cs) {
638    String parent = getPropertyByUrl(cs, "http://hl7.org/fhir/concept-properties#parent");
639    if ((parent != null)) {
640      crossLinkConcepts(cs.getConcept(), cs.getConcept(), parent);
641    }
642  }
643
644  private static String getPropertyByUrl(CodeSystem cs, String url) {
645    for (PropertyComponent pc : cs.getProperty()) {
646      if (url.equals(pc.getUri())) {
647        return pc.getCode();
648      }
649    }
650    return null;
651  }
652
653  private static void crossLinkConcepts(List<ConceptDefinitionComponent> root, List<ConceptDefinitionComponent> focus, String parent) {
654    for (ConceptDefinitionComponent def : focus) {
655      List<ConceptPropertyComponent> pcl = getPropertyValues(def, parent);
656      for (ConceptPropertyComponent pc : pcl) {
657        String code = pc.getValue().primitiveValue();
658        ConceptDefinitionComponent tgt = findCode(root, code);
659        if (!tgt.hasUserData(USER_DATA_CROSS_LINK)) {
660          tgt.setUserData(USER_DATA_CROSS_LINK, new ArrayList<>());
661        }
662        @SuppressWarnings("unchecked")
663        List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) tgt.getUserData(USER_DATA_CROSS_LINK);
664        children.add(def);
665      }      
666      if (def.hasConcept()) {
667        crossLinkConcepts(root, def.getConcept(), parent);
668      }
669    }
670    
671  }
672
673  public static boolean hasHierarchy(CodeSystem cs) {
674    for (ConceptDefinitionComponent c : cs.getConcept()) {
675      if (c.hasConcept()) {
676        return true;
677      }
678    }
679    return false;
680  }
681
682  public static void sortAllCodes(CodeSystem cs) {
683    sortAllCodes(cs.getConcept());
684  }
685
686  private static void sortAllCodes(List<ConceptDefinitionComponent> list) {
687    Collections.sort(list, new ConceptDefinitionComponentSorter());
688    for (ConceptDefinitionComponent cd : list) {
689      if (cd.hasConcept()) {
690        sortAllCodes(cd.getConcept());
691      }
692    }    
693  }
694
695  public static Coding readCoding(String jurisdiction) {    
696    return jurisdiction == null || !jurisdiction.contains("#") ?  null : new Coding().setCode(jurisdiction.substring(jurisdiction.indexOf("#")+1)).setSystem(jurisdiction.substring(0, jurisdiction.indexOf("#")));
697  }
698
699  public static SystemReference getSystemReference(String system, IWorkerContext ctxt) {
700    if (system == null) {
701      return null;
702    } if ("http://snomed.info/sct".equals(system)) {
703      return new SystemReference("SNOMED CT", "https://browser.ihtsdotools.org/");      
704    } else if ("http://loinc.org".equals(system)) {
705      return new SystemReference("LOINC", "https://loinc.org/");            
706    } else if ("http://unitsofmeasure.org".equals(system)) {
707      return new SystemReference("UCUM", "http://ucum.org");            
708    } else if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) {
709      return new SystemReference("RxNorm", "http://www.nlm.nih.gov/research/umls/rxnorm");
710    } else if (ctxt != null) {
711      CodeSystem cs = ctxt.fetchCodeSystem(system);
712      if (cs != null && cs.hasWebPath()) {
713        return new SystemReference(cs.present(), cs.getWebPath(), Utilities.isAbsoluteUrl(cs.getWebPath()));
714      } else if (cs != null) {
715        return new SystemReference(cs.present(), null);
716      }
717    }
718    return null;
719  }
720
721  public static boolean isNotCurrent(CodeSystem cs, ConceptDefinitionComponent c) {
722    return isInactive(cs, c) || isDeprecated(cs, c, false);
723  }
724
725  public static List<String> getDisplays(CodeSystem srcCS, ConceptDefinitionComponent cd) {
726    List<String> list = new ArrayList<>();
727    if (cd.hasDisplay()) {
728      list.add(cd.getDisplay());
729    }
730    for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) {
731      if (!list.contains(d.getValue())) {
732        list.add(d.getValue());
733      }
734    }
735    return list;
736  }
737
738  public static boolean checkDisplay(CodeSystem cs, ConceptDefinitionComponent cd, String display) {
739    List<String> displays = getDisplays(cs, cd);
740    for (String s : displays) {
741      if (s.equalsIgnoreCase(display)) {
742        return true;
743      }
744    }
745    return false;
746  }
747
748  public static int countCodes(CodeSystem cs) {
749    return countCodes(cs.getConcept());
750  }
751
752  private static int countCodes(List<ConceptDefinitionComponent> concept) {
753    int t = concept.size();
754    for (ConceptDefinitionComponent cd : concept) {
755      t = t + (cd.hasConcept() ?  countCodes(cd.getConcept()) : 0);
756    }
757    return t;
758  }
759
760  public static CodeSystem mergeSupplements(CodeSystem cs, List<CodeSystem> supplements) {
761    CodeSystem ret = cs.copy();
762    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
763    for (CodeSystem sup : supplements) {
764      b.append(sup.getVersionedUrl());      
765    }
766    ret.setUserData("supplements.installed", b.toString());
767
768    for (ConceptDefinitionComponent t : ret.getConcept()) {
769      mergeSupplements(ret, t, supplements);
770    }
771    return ret;
772  }
773
774  private static void mergeSupplements(CodeSystem ret, ConceptDefinitionComponent fdef, List<CodeSystem> supplements) {
775    for (CodeSystem cs : supplements) {
776      ConceptDefinitionComponent def = CodeSystemUtilities.findCode(cs.getConcept(), fdef.getCode());
777      if (def != null) {
778        for (Extension ext : def.getExtension()) {
779          fdef.addExtension(ext.copy());
780        }
781        for (ConceptDefinitionDesignationComponent d : def.getDesignation()) {
782          fdef.addDesignation(d.copy());
783        }
784        for (ConceptPropertyComponent p : def.getProperty()) {
785          PropertyComponent pd = CodeSystemUtilities.getPropertyDefinition(cs, p);
786          String code;
787          if (pd != null) {
788            code = defineProperty(ret, pd, propertyTypeForType(p.getValue()));
789          } else {
790            code = defineProperty(ret, p.getCode(), propertyTypeForType(p.getValue()));
791          }
792          fdef.addProperty().setCode(code).setValue(p.getValue()).copyExtensions(p, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status");
793        }
794      }
795      for (ConceptDefinitionComponent t : fdef.getConcept()) {
796        mergeSupplements(ret, t, supplements);
797      }      
798    }
799  }
800
801  private static PropertyType propertyTypeForType(DataType value) {
802    if (value == null) {
803      return PropertyType.NULL;
804    }
805    if (value instanceof CodeType) {
806      return PropertyType.CODE;
807    }
808    if (value instanceof CodeType) {
809      return PropertyType.CODING;
810    }
811    if (value instanceof CodeType) {
812      return PropertyType.STRING;
813    }
814    if (value instanceof CodeType) {
815      return PropertyType.INTEGER;
816    }
817    if (value instanceof CodeType) {
818      return PropertyType.BOOLEAN;
819    }
820    if (value instanceof CodeType) {
821      return PropertyType.DATETIME;
822    }
823    if (value instanceof CodeType) {
824      return PropertyType.DECIMAL;
825    }
826    throw new FHIRException("Unsupported property value for a CodeSystem Property: "+value.fhirType());
827  }
828
829  private static String defineProperty(CodeSystem cs, PropertyComponent pd, PropertyType pt) {
830    for (PropertyComponent p : cs.getProperty()) {
831      if (p.getCode().equals(pd.getCode())) {
832        if (!p.getUri().equals(pd.getUri())) {
833          throw new Error("URI mismatch for code "+pd.getCode()+" url = "+p.getUri()+" vs "+pd.getUri());
834        }
835        if (!p.getType().equals(pt)) {
836          throw new Error("Type mismatch for code "+pd.getCode()+" type = "+p.getType().toCode()+" vs "+pt.toCode());
837        }
838        return pd.getCode();
839      }
840    }
841    cs.addProperty().setCode(pd.getCode()).setUri(pd.getUri()).setType(pt);
842    return pd.getCode();
843
844  }
845
846
847  private static PropertyComponent getPropertyDefinition(CodeSystem cs, ConceptPropertyComponent p) {
848    for (PropertyComponent t : cs.getProperty()) {
849      if (t.getCode().equals(p.getCode())) {
850        return t;
851      }
852    }
853    return null;
854  }
855
856  public static boolean hasProperties(CodeSystem cs) {
857    return hasProperties(cs.getConcept());
858  }
859
860  private static boolean hasProperties(List<ConceptDefinitionComponent> list) {
861    for (ConceptDefinitionComponent c : list) {
862      if (c.hasProperty() || hasProperties(c.getConcept())) {
863        return true;
864      }
865    }
866    return false;
867  }
868
869  public static boolean hasDesignations(CodeSystem cs) {
870    return hasDesignations(cs.getConcept());
871  }
872
873  private static boolean hasDesignations(List<ConceptDefinitionComponent> list) {
874    for (ConceptDefinitionComponent c : list) {
875      if (c.hasDesignation() || hasDesignations(c.getConcept())) {
876        return true;
877      }
878    }
879    return false;
880  }
881
882  public static boolean hasPropertyDef(CodeSystem cs, String property) {
883    for (PropertyComponent pd : cs.getProperty()) {
884      if (pd.getCode().equals(property)) {
885        return true;
886      }
887    }
888    return false;
889  }
890
891  public static DataType getProperty(CodeSystem cs, String code, String property) {
892    ConceptDefinitionComponent def = getCode(cs, code);
893    return getProperty(cs, def, property);
894  }
895  
896  public static DataType getProperty(CodeSystem cs, ConceptDefinitionComponent def, String property) {
897    ConceptPropertyComponent cp = getProperty(def, property);
898    return cp == null ? null : cp.getValue();
899  }
900
901  public static boolean hasMarkdownInDefinitions(CodeSystem cs, MarkDownProcessor md) {
902    return hasMarkdownInDefinitions(cs.getConcept(), md);
903  }
904
905  private static boolean hasMarkdownInDefinitions(List<ConceptDefinitionComponent> concepts, MarkDownProcessor md) {
906    for (ConceptDefinitionComponent c : concepts) {
907      if (c.hasDefinition() && md.isProbablyMarkdown(c.getDefinition(), true)) {
908        return true;
909      }
910      if (c.hasConcept() && hasMarkdownInDefinitions(c.getConcept(), md)) {
911        return true;
912      }
913    }
914    return false;
915  }
916
917  public static String getStatus(CodeSystem cs, ConceptDefinitionComponent cc) {
918    StandardsStatus ss = ToolingExtensions.getStandardsStatus(cc);
919    if (ss == StandardsStatus.DEPRECATED || ss == StandardsStatus.WITHDRAWN) {
920      return ss.toCode();
921    }
922    DataType v = getProperty(cs, cc, "status");
923    if (v == null || !v.isPrimitive()) {
924      return null;
925    } else {
926      return v.primitiveValue();
927    }
928  }
929}
930