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.ConceptPropertyComponent;
051import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
052import org.hl7.fhir.r5.model.CodeSystem.PropertyType;
053import org.hl7.fhir.r5.model.CodeType;
054import org.hl7.fhir.r5.model.Coding;
055import org.hl7.fhir.r5.model.DataType;
056import org.hl7.fhir.r5.model.DateTimeType;
057import org.hl7.fhir.r5.model.DecimalType;
058import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
059import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter;
060import org.hl7.fhir.r5.model.Identifier;
061import org.hl7.fhir.r5.model.IntegerType;
062import org.hl7.fhir.r5.model.Meta;
063import org.hl7.fhir.r5.model.StringType;
064import org.hl7.fhir.r5.model.UriType;
065import org.hl7.fhir.r5.utils.ToolingExtensions;
066import org.hl7.fhir.utilities.StandardsStatus;
067import org.hl7.fhir.utilities.Utilities;
068
069public class CodeSystemUtilities {
070
071  public static class SystemReference {
072    private String link;
073    private String text;
074    private boolean local;
075    
076    public SystemReference(String text, String link) {
077      super();
078      this.link = link;
079      this.text = text;
080    }
081    public SystemReference(String text, String link, boolean local) {
082      super();
083      this.link = link;
084      this.text = text;
085      this.local = local;
086    }
087    
088    public String getLink() {
089      return link;
090    }
091    public String getText() {
092      return text;
093    }
094    public boolean isLocal() {
095      return local;
096    }
097    
098  }
099
100  public static class ConceptDefinitionComponentSorter implements Comparator<ConceptDefinitionComponent> {
101
102    @Override
103    public int compare(ConceptDefinitionComponent o1, ConceptDefinitionComponent o2) {
104      return o1.getCode().compareToIgnoreCase(o2.getCode());
105    }
106
107  }
108
109  public static final String USER_DATA_CROSS_LINK = "cs.utils.cross.link";
110
111  public static class CodeSystemNavigator {
112
113    private CodeSystem cs;
114    private boolean restructure;
115    private Set<String> processed = new HashSet<>();
116
117    public CodeSystemNavigator(CodeSystem cs) {
118      this.cs = cs;
119      restructure = hasExtraRelationships(cs.getConcept());
120    }
121
122    public boolean isRestructure() {
123      return restructure;
124    }
125
126    private boolean hasExtraRelationships(List<ConceptDefinitionComponent> concept) {
127      for (ConceptDefinitionComponent cd : concept) {
128        if (!getSubsumedBy(cd).isEmpty()) {
129          return true;
130        }
131        for (ConceptDefinitionComponent cdc : cd.getConcept()) {
132          if (hasExtraRelationships(cdc.getConcept())) {
133            return true;
134          }
135        }
136      }
137      return false;
138    }
139
140    public List<ConceptDefinitionComponent> getConcepts(ConceptDefinitionComponent context) {
141      if (context == null) {
142        if (restructure) {
143          List<ConceptDefinitionComponent> res = new ArrayList<>();
144          for (ConceptDefinitionComponent cd : cs.getConcept()) {
145            if (getSubsumedBy(cd).isEmpty()) {
146              res.add(cd);
147              processed.add(cd.getCode());
148            }
149          }
150          return res;
151        } else {
152          return cs.getConcept();
153        }
154      } else {
155        if (restructure) {
156          List<ConceptDefinitionComponent> res = new ArrayList<>();
157          for (ConceptDefinitionComponent cd : context.getConcept()) {
158            res.add(cd);
159            processed.add(cd.getCode());
160          }
161          for (ConceptDefinitionComponent cd : cs.getConcept()) {
162            if (getSubsumedBy(cd).contains(context.getCode()) && !processed.contains(cd.getCode())) {
163              res.add(cd);
164              processed.add(cd.getCode());
165            }
166          }
167          return res;
168        } else {
169          return context.getConcept();
170        }
171      }
172    }
173
174    private List<String> getSubsumedBy(ConceptDefinitionComponent cd) {
175      List<String> codes = new ArrayList<>();
176      for (ConceptPropertyComponent cp : cd.getProperty()) {
177        if ("subsumedBy".equals(cp.getCode())) {
178          codes.add(cp.getValue().primitiveValue());
179        }
180      }
181      return codes;
182    }
183
184    public List<ConceptDefinitionComponent> getOtherChildren(ConceptDefinitionComponent context) {
185      List<ConceptDefinitionComponent> res = new ArrayList<>();
186      for (ConceptDefinitionComponent cd : cs.getConcept()) {
187        if (getSubsumedBy(cd).contains(context.getCode()) && processed.contains(cd.getCode())) {
188          res.add(cd);
189        }
190      }
191      return res;
192    }
193  }
194
195
196  public static boolean isNotSelectable(CodeSystem cs, ConceptDefinitionComponent def) {
197    for (ConceptPropertyComponent p : def.getProperty()) {
198      if ("notSelectable".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
199        return ((BooleanType) p.getValue()).getValue();
200    }
201    return false;
202  }
203
204  public static void setNotSelectable(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError {
205    defineNotSelectableProperty(cs);
206    ConceptPropertyComponent p = getProperty(concept, "notSelectable");
207    if (p != null)
208      p.setValue(new BooleanType(true));
209    else
210      concept.addProperty().setCode("notSelectable").setValue(new BooleanType(true));    
211  }
212
213  public static void setProperty(CodeSystem cs, ConceptDefinitionComponent concept, String code, DataType value) throws FHIRFormatError {
214    defineProperty(cs, code, propertyTypeForValue(value));
215    ConceptPropertyComponent p = getProperty(concept,  code);
216    if (p != null)
217      p.setValue(value);
218    else
219      concept.addProperty().setCode(code).setValue(value);    
220  }
221  
222
223  private static PropertyType propertyTypeForValue(DataType value) {
224    if (value instanceof BooleanType) {
225      return PropertyType.BOOLEAN;
226    }
227    if (value instanceof CodeType) {
228      return PropertyType.CODE;
229    }
230    if (value instanceof Coding) {
231      return PropertyType.CODING;
232    }
233    if (value instanceof DateTimeType) {
234      return PropertyType.DATETIME;
235    }
236    if (value instanceof DecimalType) {
237      return PropertyType.DECIMAL;
238    }
239    if (value instanceof IntegerType) {
240      return PropertyType.INTEGER;
241    }
242    if (value instanceof StringType) {
243      return PropertyType.STRING;
244    }
245    throw new Error("Unknown property type "+value.getClass().getName());
246  }
247
248  private static void defineProperty(CodeSystem cs, String code, PropertyType pt) {
249    String url = "http://hl7.org/fhir/concept-properties#"+code;
250    for (PropertyComponent p : cs.getProperty()) {
251      if (p.getCode().equals(code)) {
252        if (!p.getUri().equals(url)) {
253          throw new Error("URI mismatch for code "+code+" url = "+p.getUri()+" vs "+url);
254        }
255        if (!p.getType().equals(pt)) {
256          throw new Error("Type mismatch for code "+code+" type = "+p.getType()+" vs "+pt);
257        }
258        return;
259      }
260    }
261    cs.addProperty().setCode(code).setUri(url).setType(pt).setUri(url);
262  
263  }
264
265  public static void defineNotSelectableProperty(CodeSystem cs) {
266    defineCodeSystemProperty(cs, "notSelectable", "Indicates that the code is abstract - only intended to be used as a selector for other concepts", PropertyType.BOOLEAN);
267  }
268
269
270  public enum ConceptStatus {
271    Active, Experimental, Deprecated, Retired;
272
273    public String toCode() {
274      switch (this) {
275      case Active: return "active";
276      case Experimental: return "experimental";
277      case Deprecated: return "deprecated";
278      case Retired: return "retired";
279      default: return null;
280      }
281    }
282  }
283
284  public static void setStatus(CodeSystem cs, ConceptDefinitionComponent concept, ConceptStatus status) throws FHIRFormatError {
285    defineStatusProperty(cs);
286    ConceptPropertyComponent p = getProperty(concept, "status");
287    if (p != null)
288      p.setValue(new CodeType(status.toCode()));
289    else
290      concept.addProperty().setCode("status").setValue(new CodeType(status.toCode()));    
291  }
292
293  public static void defineStatusProperty(CodeSystem cs) {
294    defineCodeSystemProperty(cs, "status", "A property that indicates the status of the concept. One of active, experimental, deprecated, retired", PropertyType.CODE);
295  }
296
297  private static void defineDeprecatedProperty(CodeSystem cs) {
298    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);
299  }
300
301  public static void defineParentProperty(CodeSystem cs) {
302    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);
303  }
304
305  public static void defineChildProperty(CodeSystem cs) {
306    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);
307  }
308
309  public static boolean isDeprecated(CodeSystem cs, ConceptDefinitionComponent def, boolean ignoreStatus)  {
310    try {
311      for (ConceptPropertyComponent p : def.getProperty()) {
312        if (!ignoreStatus) {
313          if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode()))
314            return true;
315        }
316        // this, though status should also be set
317        if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 
318          return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance()));
319        // legacy  
320        if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
321          return ((BooleanType) p.getValue()).getValue();
322      }
323      StandardsStatus ss = ToolingExtensions.getStandardsStatus(def);
324      if (ss == StandardsStatus.DEPRECATED) {
325        return true;
326      }
327      return false;
328    } catch (FHIRException e) {
329      return false;
330    }
331  }
332
333  public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def, boolean ignoreStatus)  {
334    try {
335      for (ConceptPropertyComponent p : def.getProperty()) {
336        if (!ignoreStatus) {
337          if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "inactive".equals(p.getValueCodeType().getCode()))
338            return true;
339        }
340        // legacy  
341        if ("inactive".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
342          return ((BooleanType) p.getValue()).getValue();
343      }
344      return false;
345    } catch (FHIRException e) {
346      return false;
347    }
348  }
349
350  public static void setDeprecated(CodeSystem cs, ConceptDefinitionComponent concept, DateTimeType date) throws FHIRFormatError {
351    setStatus(cs, concept, ConceptStatus.Deprecated);
352    defineDeprecatedProperty(cs);
353    concept.addProperty().setCode("deprecationDate").setValue(date);    
354  }
355
356
357  public static void setDeprecated(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError {
358    setStatus(cs, concept, ConceptStatus.Deprecated);
359  }
360  
361  public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def) throws FHIRException {
362    for (ConceptPropertyComponent p : def.getProperty()) {
363      if ("status".equals(p.getCode()) && p.hasValueStringType()) {
364        return "inactive".equals(p.getValueStringType().primitiveValue()) || "retired".equals(p.getValueStringType().primitiveValue());
365      }
366      if ("inactive".equals(p.getCode()) && p.hasValueBooleanType()) {
367        return p.getValueBooleanType().getValue();
368      }
369    }
370    return false;
371  }
372  
373  public static boolean isInactive(CodeSystem cs, String code) throws FHIRException {
374    ConceptDefinitionComponent def = findCode(cs.getConcept(), code);
375    if (def == null)
376      return true;
377    return isInactive(cs, def);
378  }
379
380  public static void defineCodeSystemProperty(CodeSystem cs, String code, String description, PropertyType type) {
381    for (PropertyComponent p : cs.getProperty()) {
382      if (p.getCode().equals(code))
383        return;
384    }
385    cs.addProperty().setCode(code).setDescription(description).setType(type).setUri("http://hl7.org/fhir/concept-properties#"+code);
386  }
387
388  public static String getCodeDefinition(CodeSystem cs, String code) {
389    return getCodeDefinition(cs.getConcept(), code);
390  }
391
392  private static String getCodeDefinition(List<ConceptDefinitionComponent> list, String code) {
393    for (ConceptDefinitionComponent c : list) {
394      if (c.hasCode() &&  c.getCode().equals(code))
395        return c.getDefinition();
396      String s = getCodeDefinition(c.getConcept(), code);
397      if (s != null)
398        return s;
399    }
400    return null;
401  }
402
403  public static CodeSystem makeShareable(CodeSystem cs) {
404    if (!cs.hasExperimental()) {
405      cs.setExperimental(false);
406    }
407
408    if (!cs.hasMeta())
409      cs.setMeta(new Meta());
410    for (UriType t : cs.getMeta().getProfile()) 
411      if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue()))
412        return cs;
413    cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
414    return cs;
415  }
416
417  public static boolean makeCSShareable(CodeSystem cs) {
418    if (!cs.hasMeta())
419      cs.setMeta(new Meta());
420    for (UriType t : cs.getMeta().getProfile()) 
421      if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue()))
422        return false;
423    cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
424    return true;
425  }
426
427  public static void setOID(CodeSystem cs, String oid) {
428    if (!oid.startsWith("urn:oid:"))
429       oid = "urn:oid:" + oid;
430    if (!cs.hasIdentifier())
431      cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid));
432    else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
433      cs.getIdentifierFirstRep().setValue(oid);
434    else
435      throw new Error("unable to set OID on code system");
436    
437  }
438
439  public static boolean hasOID(CanonicalResource cs) {
440    return getOID(cs) != null;
441  }
442
443  public static String getOID(CanonicalResource cs) {
444    if (cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
445        return cs.getIdentifierFirstRep().getValue().substring(8);
446    return null;
447  }
448
449  public static ConceptDefinitionComponent findCode(List<ConceptDefinitionComponent> list, String code) {
450    for (ConceptDefinitionComponent c : list) {
451      if (c.getCode().equals(code))
452        return c;
453      ConceptDefinitionComponent s = findCode(c.getConcept(), code);
454      if (s != null)
455        return s;
456    }
457    return null;
458  }
459
460  public static void markStatus(CodeSystem cs, String wg, StandardsStatus status, String pckage, String fmm, String normativeVersion) throws FHIRException {
461    if (wg != null) {
462      if (!ToolingExtensions.hasExtension(cs, ToolingExtensions.EXT_WORKGROUP) || 
463          (Utilities.existsInList(ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) {
464        ToolingExtensions.setCodeExtension(cs, ToolingExtensions.EXT_WORKGROUP, wg);
465      }
466    }
467    if (status != null) {
468      StandardsStatus ss = ToolingExtensions.getStandardsStatus(cs);
469      if (ss == null || ss.isLowerThan(status)) 
470        ToolingExtensions.setStandardsStatus(cs, status, normativeVersion);
471      if (pckage != null) {
472        if (!cs.hasUserData("ballot.package"))
473          cs.setUserData("ballot.package", pckage);
474        else if (!pckage.equals(cs.getUserString("ballot.package")))
475          if (!"infrastructure".equals(cs.getUserString("ballot.package")))
476            System.out.println("Code System "+cs.getUrl()+": ownership clash "+pckage+" vs "+cs.getUserString("ballot.package"));
477      }
478      if (status == StandardsStatus.NORMATIVE) {
479        cs.setExperimental(false);
480        cs.setStatus(PublicationStatus.ACTIVE);
481      }
482    }
483    if (fmm != null) {
484      String sfmm = ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_FMM_LEVEL);
485      if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 
486        ToolingExtensions.setIntegerExtension(cs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm));
487      }
488      if (Integer.parseInt(fmm) <= 1) {
489        cs.setExperimental(true);
490      }
491    }
492  }
493
494 
495  public static DataType readProperty(ConceptDefinitionComponent concept, String code) {
496    for (ConceptPropertyComponent p : concept.getProperty())
497      if (p.getCode().equals(code))
498        return p.getValue(); 
499    return null;
500  }
501
502  public static ConceptPropertyComponent getProperty(ConceptDefinitionComponent concept, String code) {
503    for (ConceptPropertyComponent p : concept.getProperty())
504      if (p.getCode().equals(code))
505        return p; 
506    return null;
507  }
508  
509  public static List<ConceptPropertyComponent> getPropertyValues(ConceptDefinitionComponent concept, String code) {
510    List<ConceptPropertyComponent> res = new ArrayList<>();
511    for (ConceptPropertyComponent p : concept.getProperty()) {
512      if (p.getCode().equals(code)) {
513        res.add(p); 
514      }
515    }
516    return res;
517  }
518
519
520  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
521  // returns additional parents not in the heirarchy
522  public static List<String> getOtherChildren(CodeSystem cs, ConceptDefinitionComponent c) {
523    List<String> res = new ArrayList<String>();
524    for (ConceptPropertyComponent p : c.getProperty()) {
525      if ("parent".equals(p.getCode())) {
526        res.add(p.getValue().primitiveValue());
527      }
528    }
529    return res;
530  }
531
532  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
533  public static void addOtherChild(CodeSystem cs, ConceptDefinitionComponent owner, String code) {
534    defineChildProperty(cs);
535    owner.addProperty().setCode("child").setValue(new CodeType(code));
536  }
537
538  public static boolean hasProperty(ConceptDefinitionComponent c, String code) {
539    for (ConceptPropertyComponent cp : c.getProperty()) {
540      if (code.equals(cp.getCode())) {
541        return true;
542      }
543    }
544    return false;
545  }
546
547  public static boolean hasCode(CodeSystem cs, String code) {
548    for (ConceptDefinitionComponent cc : cs.getConcept()) {
549      if (hasCode(cc, code)) {
550        return true;
551      }
552    }
553    return false;
554  }
555
556  private static boolean hasCode(ConceptDefinitionComponent cc, String code) {
557    if (code.equals(cc.getCode())) {
558      return true;
559    }
560    for (ConceptDefinitionComponent c : cc.getConcept()) {
561      if (hasCode(c, code)) {
562        return true;
563      }
564    }
565    return false;
566  }
567
568  public static ConceptDefinitionComponent getCode(CodeSystem cs, String code) {
569    if (code == null) {
570      return null;
571    }
572    for (ConceptDefinitionComponent cc : cs.getConcept()) {
573      ConceptDefinitionComponent cd = getCode(cc, code);
574      if (cd != null) {
575        return cd;
576      }
577    }
578    return null;
579  }
580
581  private static ConceptDefinitionComponent getCode(ConceptDefinitionComponent cc, String code) {
582    if (code.equals(cc.getCode())) {
583      return cc;
584    }
585    for (ConceptDefinitionComponent c : cc.getConcept()) {
586      ConceptDefinitionComponent cd = getCode(c, code);
587      if (cd != null) {
588        return cd;
589      }
590    }
591    return null;
592  }
593
594  public static void crossLinkCodeSystem(CodeSystem cs) {
595    String parent = getPropertyByUrl(cs, "http://hl7.org/fhir/concept-properties#parent");
596    if ((parent != null)) {
597      crossLinkConcepts(cs.getConcept(), cs.getConcept(), parent);
598    }
599  }
600
601  private static String getPropertyByUrl(CodeSystem cs, String url) {
602    for (PropertyComponent pc : cs.getProperty()) {
603      if (url.equals(pc.getUri())) {
604        return pc.getCode();
605      }
606    }
607    return null;
608  }
609
610  private static void crossLinkConcepts(List<ConceptDefinitionComponent> root, List<ConceptDefinitionComponent> focus, String parent) {
611    for (ConceptDefinitionComponent def : focus) {
612      List<ConceptPropertyComponent> pcl = getPropertyValues(def, parent);
613      for (ConceptPropertyComponent pc : pcl) {
614        String code = pc.getValue().primitiveValue();
615        ConceptDefinitionComponent tgt = findCode(root, code);
616        if (!tgt.hasUserData(USER_DATA_CROSS_LINK)) {
617          tgt.setUserData(USER_DATA_CROSS_LINK, new ArrayList<>());
618        }
619        @SuppressWarnings("unchecked")
620        List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) tgt.getUserData(USER_DATA_CROSS_LINK);
621        children.add(def);
622      }      
623      if (def.hasConcept()) {
624        crossLinkConcepts(root, def.getConcept(), parent);
625      }
626    }
627    
628  }
629
630  public static boolean hasHierarchy(CodeSystem cs) {
631    for (ConceptDefinitionComponent c : cs.getConcept()) {
632      if (c.hasConcept()) {
633        return true;
634      }
635    }
636    return false;
637  }
638
639  public static void sortAllCodes(CodeSystem cs) {
640    sortAllCodes(cs.getConcept());
641  }
642
643  private static void sortAllCodes(List<ConceptDefinitionComponent> list) {
644    Collections.sort(list, new ConceptDefinitionComponentSorter());
645    for (ConceptDefinitionComponent cd : list) {
646      if (cd.hasConcept()) {
647        sortAllCodes(cd.getConcept());
648      }
649    }    
650  }
651
652  public static Coding readCoding(String jurisdiction) {    
653    return jurisdiction == null || !jurisdiction.contains("#") ?  null : new Coding().setCode(jurisdiction.substring(jurisdiction.indexOf("#")+1)).setSystem(jurisdiction.substring(0, jurisdiction.indexOf("#")));
654  }
655
656  public static SystemReference getSystemReference(String system, IWorkerContext ctxt) {
657    if (system == null) {
658      return null;
659    } if ("http://snomed.info/sct".equals(system)) {
660      return new SystemReference("SNOMED CT", "https://browser.ihtsdotools.org/");      
661    } else if ("http://loinc.org".equals(system)) {
662      return new SystemReference("LOINC", "https://loinc.org/");            
663    } else if ("http://unitsofmeasure.org".equals(system)) {
664      return new SystemReference("UCUM", "http://ucum.org");            
665    } else if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) {
666      return new SystemReference("RxNorm", "http://www.nlm.nih.gov/research/umls/rxnorm");
667    } else if (ctxt != null) {
668      CodeSystem cs = ctxt.fetchCodeSystem(system);
669      if (cs != null && cs.hasUserData("path")) {
670        return new SystemReference(cs.present(), cs.getUserString("path"), Utilities.isAbsoluteUrl(cs.getUserString("path")));
671      } else if (cs != null) {
672        return new SystemReference(cs.present(), null);
673      }
674    }
675    return null;
676  }
677
678  public static boolean isNotCurrent(CodeSystem cs, ConceptDefinitionComponent c) {
679    return isInactive(cs, c) || isDeprecated(cs, c, false);
680  }
681}
682