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.Collection;
037import java.util.Collections;
038import java.util.Comparator;
039import java.util.HashSet;
040import java.util.List;
041import java.util.Set;
042
043import lombok.extern.slf4j.Slf4j;
044import org.hl7.fhir.exceptions.FHIRException;
045import org.hl7.fhir.exceptions.FHIRFormatError;
046import org.hl7.fhir.r5.context.IWorkerContext;
047import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
048import org.hl7.fhir.r5.extensions.ExtensionUtilities;
049import org.hl7.fhir.r5.model.*;
050import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
051import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
052import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
053import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
054import org.hl7.fhir.r5.model.CodeSystem.PropertyType;
055import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
056import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter;
057import org.hl7.fhir.r5.terminologies.providers.SpecialCodeSystem;
058import org.hl7.fhir.r5.utils.CanonicalResourceUtilities;
059
060import org.hl7.fhir.r5.utils.UserDataNames;
061import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
062import org.hl7.fhir.utilities.MarkDownProcessor;
063import org.hl7.fhir.utilities.StandardsStatus;
064import org.hl7.fhir.utilities.Utilities;
065import org.hl7.fhir.utilities.VersionUtilities;
066
067import javax.annotation.Nonnull;
068
069@Slf4j
070public class CodeSystemUtilities extends TerminologyUtilities {
071
072  public static class CodeSystemSorter implements Comparator<CodeSystem> {
073
074    @Override
075    public int compare(CodeSystem o1, CodeSystem o2) {
076      String url1 = o1.getUrl();
077      String url2 = o2.getUrl();
078      int c = compareString(url1, url2);
079      if (c == 0) {
080        String ver1 = o1.getVersion();
081        String ver2 = o2.getVersion();
082        c = VersionUtilities.compareVersions(ver1, ver2);
083        if (c == 0) {
084          String d1 = o1.getDateElement().asStringValue();
085          String d2 = o2.getDateElement().asStringValue();
086          c = compareString(url1, url2);
087        }
088      }
089      return c;
090    }
091
092    private int compareString(String s1, String s2) {
093      if (s1 == null) {
094        return s2 == null ? 0 : 1;
095      } else {
096        return s1.compareTo(s2);
097      }
098    }
099
100  }
101
102
103  
104  public static class SystemReference {
105    private String link;
106    private String text;
107    private boolean local;
108    
109    public SystemReference(String text, String link) {
110      super();
111      this.link = link;
112      this.text = text;
113    }
114    public SystemReference(String text, String link, boolean local) {
115      super();
116      this.link = link;
117      this.text = text;
118      this.local = local;
119    }
120    
121    public String getLink() {
122      return link;
123    }
124    public String getText() {
125      return text;
126    }
127    public boolean isLocal() {
128      return local;
129    }
130    
131  }
132
133  public static class ConceptDefinitionComponentSorter implements Comparator<ConceptDefinitionComponent> {
134
135    @Override
136    public int compare(ConceptDefinitionComponent o1, ConceptDefinitionComponent o2) {
137      return o1.hasCode() ? o1.getCode().compareToIgnoreCase(o2.getCode()) : 0;
138    }
139
140  }
141
142  public static final String USER_DATA_CROSS_LINK = "cs.utils.cross.link";
143
144  public static class CodeSystemNavigator {
145
146    private CodeSystem cs;
147    private boolean restructure;
148    private Set<String> processed = new HashSet<>();
149
150    public CodeSystemNavigator(CodeSystem cs) {
151      this.cs = cs;
152      restructure = hasExtraRelationships(cs.getConcept());
153    }
154
155    public boolean isRestructure() {
156      return restructure;
157    }
158
159    private boolean hasExtraRelationships(List<ConceptDefinitionComponent> concept) {
160      for (ConceptDefinitionComponent cd : concept) {
161        if (!getSubsumedBy(cd).isEmpty()) {
162          return true;
163        }
164        for (ConceptDefinitionComponent cdc : cd.getConcept()) {
165          if (hasExtraRelationships(cdc.getConcept())) {
166            return true;
167          }
168        }
169      }
170      return false;
171    }
172
173    public List<ConceptDefinitionComponent> getConcepts(@Nonnull ConceptDefinitionComponent context) {
174      if (context == null) {
175        if (restructure) {
176          List<ConceptDefinitionComponent> res = new ArrayList<>();
177          for (ConceptDefinitionComponent cd : cs.getConcept()) {
178            if (getSubsumedBy(cd).isEmpty()) {
179              res.add(cd);
180              processed.add(cd.getCode());
181            }
182          }
183          return res;
184        } else {
185          return cs.getConcept();
186        }
187      } else {
188        if (restructure) {
189          List<ConceptDefinitionComponent> res = new ArrayList<>();
190          for (ConceptDefinitionComponent cd : context.getConcept()) {
191            res.add(cd);
192            processed.add(cd.getCode());
193          }
194          for (ConceptDefinitionComponent cd : cs.getConcept()) {
195            if (getSubsumedBy(cd).contains(context.getCode()) && !processed.contains(cd.getCode())) {
196              res.add(cd);
197              processed.add(cd.getCode());
198            }
199          }
200          return res;
201        } else {
202          return context.getConcept();
203        }
204      }
205    }
206
207    private List<String> getSubsumedBy(ConceptDefinitionComponent cd) {
208      List<String> codes = new ArrayList<>();
209      for (ConceptPropertyComponent cp : cd.getProperty()) {
210        if ("subsumedBy".equals(cp.getCode())) {
211          codes.add(cp.getValue().primitiveValue());
212        }
213      }
214      return codes;
215    }
216
217    public List<ConceptDefinitionComponent> getOtherChildren(@Nonnull ConceptDefinitionComponent context) {
218      List<ConceptDefinitionComponent> res = new ArrayList<>();
219      for (ConceptDefinitionComponent cd : cs.getConcept()) {
220        if (getSubsumedBy(cd).contains(context.getCode()) && processed.contains(cd.getCode())) {
221          res.add(cd);
222        }
223      }
224      return res;
225    }
226  }
227
228
229  public static boolean isNotSelectable(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def) {
230    String pd = getPropertyByUrl(cs, "http://hl7.org/fhir/concept-properties#notSelectable");
231    if (pd == null) {
232      pd = "notSelectable";
233    }
234    for (ConceptPropertyComponent p : def.getProperty()) {
235      if (pd.equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
236        return ((BooleanType) p.getValue()).getValue();
237    }
238    return false;
239  }
240
241  public static boolean isNotSelectable(@Nonnull CodeSystem cs, @Nonnull String code) {
242    ConceptDefinitionComponent cd = findCode(cs.getConcept(), code);
243    return cd == null ? false : isNotSelectable(cs, cd);
244  }
245
246  public static void setNotSelectable(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept) throws FHIRFormatError {
247    defineNotSelectableProperty(cs);
248    ConceptPropertyComponent p = getProperty(concept, "notSelectable");
249    if (p != null)
250      p.setValue(new BooleanType(true));
251    else
252      concept.addProperty().setCode("notSelectable").setValue(new BooleanType(true));    
253  }
254
255  public static void setProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept, @Nonnull String code, @Nonnull DataType value) throws FHIRFormatError {
256    defineProperty(cs, code, propertyTypeForValue(value));
257    ConceptPropertyComponent p = getProperty(concept,  code);
258    if (p != null)
259      p.setValue(value);
260    else
261      concept.addProperty().setCode(code).setValue(value);    
262  }
263  
264  public static void setProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept, @Nonnull String url, @Nonnull String code, @Nonnull DataType value) throws FHIRFormatError {
265    defineProperty(cs, code, propertyTypeForValue(value), url);
266    ConceptPropertyComponent p = getProperty(concept,  code);
267    if (p != null)
268      p.setValue(value);
269    else
270      concept.addProperty().setCode(code).setValue(value);    
271  }
272  
273
274  private static PropertyType propertyTypeForValue(DataType value) {
275    if (value instanceof BooleanType) {
276      return PropertyType.BOOLEAN;
277    }
278    if (value instanceof CodeType) {
279      return PropertyType.CODE;
280    }
281    if (value instanceof Coding) {
282      return PropertyType.CODING;
283    }
284    if (value instanceof DateTimeType) {
285      return PropertyType.DATETIME;
286    }
287    if (value instanceof DecimalType) {
288      return PropertyType.DECIMAL;
289    }
290    if (value instanceof IntegerType) {
291      return PropertyType.INTEGER;
292    }
293    if (value instanceof StringType) {
294      return PropertyType.STRING;
295    }
296    throw new Error("Unknown property type "+value.getClass().getName());
297  }
298
299  private static String defineProperty(CodeSystem cs, String code, PropertyType pt) {
300    String url = "http://hl7.org/fhir/concept-properties#"+code;
301    return defineProperty(cs, code, pt, url);
302  }
303  private static String defineProperty(CodeSystem cs, String code, PropertyType pt, String url) {
304    for (PropertyComponent p : cs.getProperty()) {
305      if (p.hasCode() && p.getCode().equals(code)) {
306        if (!p.getUri().equals(url)) {
307          throw new Error("URI mismatch for code "+code+" url = "+p.getUri()+" vs "+url);
308        }
309        if (!p.getType().equals(pt)) {
310          throw new Error("Type mismatch for code "+code+" type = "+p.getType()+" vs "+pt);
311        }
312        return code;
313      }
314    }
315    cs.addProperty().setCode(code).setUri(url).setType(pt).setUri(url);
316    return code;
317  }
318
319  public static void defineNotSelectableProperty(@Nonnull CodeSystem cs) {
320    defineCodeSystemProperty(cs, "notSelectable", "Indicates that the code is abstract - only intended to be used as a selector for other concepts", PropertyType.BOOLEAN);
321  }
322
323
324  public enum ConceptStatus {
325    Active, Experimental, Deprecated, Retired;
326
327    public String toCode() {
328      switch (this) {
329      case Active: return "active";
330      case Experimental: return "experimental";
331      case Deprecated: return "deprecated";
332      case Retired: return "retired";
333      default: return null;
334      }
335    }
336  }
337
338  public static void setStatus(@Nonnull CodeSystem cs, ConceptDefinitionComponent concept, ConceptStatus status) throws FHIRFormatError {
339    defineStatusProperty(cs);
340    ConceptPropertyComponent p = getProperty(concept, "status");
341    if (p != null)
342      p.setValue(new CodeType(status.toCode()));
343    else
344      concept.addProperty().setCode("status").setValue(new CodeType(status.toCode()));    
345  }
346
347  public static void defineStatusProperty(@Nonnull CodeSystem cs) {
348    defineCodeSystemProperty(cs, "status", "A property that indicates the status of the concept. One of active, experimental, deprecated, retired", PropertyType.CODE);
349  }
350
351  private static void defineDeprecatedProperty(CodeSystem cs) {
352    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);
353  }
354
355  public static void defineParentProperty(@Nonnull CodeSystem cs) {
356    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);
357  }
358
359  public static void defineChildProperty(@Nonnull CodeSystem cs) {
360    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);
361  }
362
363  public static boolean isDeprecated(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def, boolean ignoreStatus)  {
364    try {
365      for (ConceptPropertyComponent p : def.getProperty()) {
366        if (!ignoreStatus) {
367          if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode()))
368            return true;
369        }
370        // this, though status should also be set
371        if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 
372          return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance()));
373        // legacy  
374        if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
375          return ((BooleanType) p.getValue()).getValue();
376      }
377      StandardsStatus ss = ExtensionUtilities.getStandardsStatus(def);
378      if (ss == StandardsStatus.DEPRECATED) {
379        return true;
380      }
381      return false;
382    } catch (FHIRException e) {
383      return false;
384    }
385  }
386
387  public static boolean isInactive(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def, boolean ignoreStatus)  {
388    try {
389      for (ConceptPropertyComponent p : def.getProperty()) {
390        if (!ignoreStatus) {
391          if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "inactive".equals(p.getValueCodeType().getCode()))
392            return true;
393        }
394        // legacy  
395        if ("inactive".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
396          return ((BooleanType) p.getValue()).getValue();
397      }
398      return false;
399    } catch (FHIRException e) {
400      return false;
401    }
402  }
403
404  public static void setDeprecated(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept, @Nonnull DateTimeType date) throws FHIRFormatError {
405    setStatus(cs, concept, ConceptStatus.Deprecated);
406    defineDeprecatedProperty(cs);
407    concept.addProperty().setCode("deprecationDate").setValue(date);    
408  }
409
410
411  public static void setDeprecated(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept) throws FHIRFormatError {
412    setStatus(cs, concept, ConceptStatus.Deprecated);
413  }
414  
415  public static boolean isInactive(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def) throws FHIRException {
416    StandardsStatus ss = ExtensionUtilities.getStandardsStatus(def);
417    if (ss == StandardsStatus.WITHDRAWN) {
418      return true;
419    }
420    for (ConceptPropertyComponent p : def.getProperty()) {
421      if ("status".equals(p.getCode()) && p.hasValueStringType()) {
422        return Utilities.existsInList(p.getValueStringType().primitiveValue(), "inactive", "retired");
423      }
424      if ("inactive".equals(p.getCode()) && p.hasValueBooleanType()) {
425        return p.getValueBooleanType().getValue();
426      }
427      if ("inactive".equals(p.getCode()) && p.hasValueCodeType()) {
428        String code = p.getValueCodeType().primitiveValue();
429        return "true".equals(code);
430      }
431    }
432    return false;
433  }
434  
435  public static boolean isInactive(@Nonnull CodeSystem cs, @Nonnull String code) throws FHIRException {
436    if (cs.hasUserData(UserDataNames.tx_cs_special)) {
437      SpecialCodeSystem scs = (SpecialCodeSystem) cs.getUserData(UserDataNames.tx_cs_special);
438      return scs.inactive(code);
439    }
440    ConceptDefinitionComponent def = findCode(cs.getConcept(), code);
441    if (def == null)
442      return true;
443    return isInactive(cs, def);
444  }
445
446  public static void defineCodeSystemProperty(@Nonnull CodeSystem cs, @Nonnull String code, @Nonnull String description, @Nonnull PropertyType type) {
447    for (PropertyComponent p : cs.getProperty()) {
448      if (p.hasCode() && p.getCode().equals(code))
449        return;
450    }
451    cs.addProperty().setCode(code).setDescription(description).setType(type).setUri("http://hl7.org/fhir/concept-properties#"+code);
452  }
453
454  public static String getCodeDefinition(@Nonnull CodeSystem cs, @Nonnull String code) {
455    return getCodeDefinition(cs.getConcept(), code);
456  }
457
458  private static String getCodeDefinition(List<ConceptDefinitionComponent> list, String code) {
459    for (ConceptDefinitionComponent c : list) {
460      if (c.hasCode() &&  c.getCode().equals(code))
461        return c.getDefinition();
462      String s = getCodeDefinition(c.getConcept(), code);
463      if (s != null)
464        return s;
465    }
466    return null;
467  }
468
469  public static boolean makeShareable(@Nonnull CodeSystem cs, boolean extension) {
470    boolean changed = false;
471    if (!cs.hasExperimental()) {
472      cs.setExperimental(false);
473      changed = true;
474    }
475
476    if (extension) {
477      if (!cs.hasMeta())
478        cs.setMeta(new Meta());
479      for (UriType t : cs.getMeta().getProfile())
480        if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue()))
481          return changed;
482      cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
483    }
484    return changed;
485  }
486
487  public static void setOID(@Nonnull CodeSystem cs, @Nonnull String oid) {
488    if (!oid.startsWith("urn:oid:"))
489       oid = "urn:oid:" + oid;
490    if (!cs.hasIdentifier())
491      cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid));
492    else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
493      cs.getIdentifierFirstRep().setValue(oid);
494    else
495      throw new Error("unable to set OID on code system");
496    
497  }
498
499  public static boolean hasOID(@Nonnull CanonicalResource cs) {
500    return getOID(cs) != null;
501  }
502
503  public static String getOID(@Nonnull CanonicalResource cs) {
504    if (cs != null && cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
505        return cs.getIdentifierFirstRep().getValue().substring(8);
506    return null;
507  }
508
509  public static ConceptDefinitionComponent findCode(@Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code) {
510    for (ConceptDefinitionComponent c : list) {
511      if (c.hasCode() && c.getCode().equals(code))
512        return c;
513      ConceptDefinitionComponent s = findCode(c.getConcept(), code);
514      if (s != null)
515        return s;
516    }
517    return null;
518  }
519
520
521  public static List<ConceptDefinitionComponent> findCodeWithParents(@Nonnull List<ConceptDefinitionComponent> parents, @Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code) {
522    for (ConceptDefinitionComponent c : list) {
523      if (c.hasCode() && c.getCode().equals(code)) {
524        return addToList(parents, c);
525      }
526      List<ConceptDefinitionComponent> s = findCodeWithParents(addToList(parents, c), c.getConcept(), code);
527      if (s != null)
528        return s;
529    }
530    return null;
531  }
532
533  private static List<ConceptDefinitionComponent> addToList(List<ConceptDefinitionComponent> parents, ConceptDefinitionComponent c) {
534    List<ConceptDefinitionComponent> res = new ArrayList<CodeSystem.ConceptDefinitionComponent>();
535    if (parents != null) {
536      res.addAll(parents);
537    }
538    res.add(c);
539    return res;
540  }
541
542  public static ConceptDefinitionComponent findCodeOrAltCode(@Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code, @Nonnull String use) {
543    for (ConceptDefinitionComponent c : list) {
544      if (c.hasCode() && c.getCode().equals(code))
545        return c;
546      for (ConceptPropertyComponent p : c.getProperty()) {
547        if ("alternateCode".equals(p.getCode()) && (use == null || hasUse(p, use)) && p.hasValue() && p.getValue().isPrimitive() && code.equals(p.getValue().primitiveValue())) {
548          return c;
549        }
550      }
551      ConceptDefinitionComponent s = findCodeOrAltCode(c.getConcept(), code, use);
552      if (s != null)
553        return s;
554    }
555    return null;
556  }
557
558  private static boolean hasUse(ConceptPropertyComponent p, String use) {
559    for (Extension ext : p.getExtensionsByUrl(ExtensionDefinitions.EXT_CS_ALTERNATE_USE)) {
560      if (ext.hasValueCoding() && use.equals(ext.getValueCoding().getCode())) {
561        return true;
562      }
563    }
564    return false;
565  }
566
567  public static void markStatus(@Nonnull CodeSystem cs, String wg, StandardsStatus status, String fmm, String normativeVersion) throws FHIRException {
568    if (wg != null) {
569      if (!ExtensionUtilities.hasExtension(cs, ExtensionDefinitions.EXT_WORKGROUP) || 
570          (Utilities.existsInList(ExtensionUtilities.readStringExtension(cs, ExtensionDefinitions.EXT_WORKGROUP), "fhir", "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) {
571        CanonicalResourceUtilities.setHl7WG(cs, wg);
572      }
573    }
574    if (status != null) {
575      StandardsStatus ss = ExtensionUtilities.getStandardsStatus(cs);
576      if (ss == null || ss.isLowerThan(status)) 
577        ExtensionUtilities.setStandardsStatus(cs, status, normativeVersion);
578      if (status == StandardsStatus.NORMATIVE) {
579        cs.setStatus(PublicationStatus.ACTIVE);
580      }
581    }
582    if (fmm != null) {
583      String sfmm = ExtensionUtilities.readStringExtension(cs, ExtensionDefinitions.EXT_FMM_LEVEL);
584      if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 
585        ExtensionUtilities.setIntegerExtension(cs, ExtensionDefinitions.EXT_FMM_LEVEL, Integer.parseInt(fmm));
586      }
587    }
588  }
589
590 
591  public static DataType readProperty(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) {
592    for (ConceptPropertyComponent p : concept.getProperty())
593      if (p.hasCode() && p.getCode().equals(code))
594        return p.getValue(); 
595    return null;
596  }
597
598  public static ConceptPropertyComponent getProperty(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) {
599    for (ConceptPropertyComponent p : concept.getProperty())
600      if (p.hasCode() && p.getCode().equals(code))
601        return p; 
602    return null;
603  }
604  
605  public static List<ConceptPropertyComponent> getPropertyValues(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) {
606    List<ConceptPropertyComponent> res = new ArrayList<>();
607    if (code != null) {
608      for (ConceptPropertyComponent p : concept.getProperty()) {
609        if (code.equals(p.getCode())) {
610          res.add(p); 
611        }
612      }
613    }
614    return res;
615  }
616
617  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
618  // returns additional parents not in the hierarchy
619  public static List<String> getOtherChildren(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) {
620    List<String> res = new ArrayList<String>();
621    for (ConceptPropertyComponent p : c.getProperty()) {
622      if ("parent".equals(p.getCode())) {
623        res.add(p.getValue().primitiveValue());
624      }
625    }
626    return res;
627  }
628
629  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
630  public static void addOtherChild(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent owner, @Nonnull String code) {
631    defineChildProperty(cs);
632    owner.addProperty().setCode("child").setValue(new CodeType(code));
633  }
634
635  public static boolean hasProperty(@Nonnull ConceptDefinitionComponent c, @Nonnull String code) {
636    for (ConceptPropertyComponent cp : c.getProperty()) {
637      if (code.equals(cp.getCode())) {
638        return true;
639      }
640    }
641    return false;
642  }
643
644  public static String getProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c, @Nonnull String uri, @Nonnull String code) {
645    for (ConceptPropertyComponent cp : c.getProperty()) {
646      if (code.equals(cp.getCode())) {
647        return cp.getValue().primitiveValue();
648      }
649    }
650    for (PropertyComponent p : cs.getProperty()) {
651      if (uri.equals(p.getUri())) {
652        var t = getProperty(c, p.getCode());
653        if (t != null) {
654          return t.primitiveValue();
655        }
656      }
657    }
658    return null;
659  }
660
661  public static boolean hasProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c, @Nonnull String uri, @Nonnull String code) {
662    for (ConceptPropertyComponent cp : c.getProperty()) {
663      if (code.equals(cp.getCode())) {
664        return true;
665      }
666    }
667    for (PropertyComponent p : cs.getProperty()) {
668      if (uri.equals(p.getUri())) {
669        return hasProperty(c, p.getCode());
670      }
671    }
672    return false;
673  }
674
675  public static boolean hasCode(@Nonnull CodeSystem cs, @Nonnull String code) {
676    for (ConceptDefinitionComponent cc : cs.getConcept()) {
677      if (hasCode(cc, code)) {
678        return true;
679      }
680    }
681    return false;
682  }
683
684  private static boolean hasCode(@Nonnull ConceptDefinitionComponent cc, @Nonnull String code) {
685    if (code.equals(cc.getCode())) {
686      return true;
687    }
688    for (ConceptDefinitionComponent c : cc.getConcept()) {
689      if (hasCode(c, code)) {
690        return true;
691      }
692    }
693    return false;
694  }
695
696  public static ConceptDefinitionComponent getCode(@Nonnull CodeSystem cs, @Nonnull String code) {
697    for (ConceptDefinitionComponent cc : cs.getConcept()) {
698      ConceptDefinitionComponent cd = getCode(cc, code);
699      if (cd != null) {
700        return cd;
701      }
702    }
703    return null;
704  }
705
706  private static ConceptDefinitionComponent getCode(@Nonnull ConceptDefinitionComponent cc, @Nonnull String code) {
707    if (code.equals(cc.getCode())) {
708      return cc;
709    }
710    for (ConceptDefinitionComponent c : cc.getConcept()) {
711      ConceptDefinitionComponent cd = getCode(c, code);
712      if (cd != null) {
713        return cd;
714      }
715    }
716    return null;
717  }
718
719  public static void crossLinkCodeSystem(@Nonnull CodeSystem cs) {
720    String parent = getPropertyByUrl(cs, "http://hl7.org/fhir/concept-properties#parent");
721    if ((parent != null)) {
722      crossLinkConcepts(cs.getConcept(), cs.getConcept(), parent);
723    }
724  }
725
726  private static String getPropertyByUrl(@Nonnull CodeSystem cs, String url) {
727    for (PropertyComponent pc : cs.getProperty()) {
728      if (url.equals(pc.getUri())) {
729        return pc.getCode();
730      }
731    }
732    return null;
733  }
734
735  private static void crossLinkConcepts(List<ConceptDefinitionComponent> root, List<ConceptDefinitionComponent> focus, String parent) {
736    for (ConceptDefinitionComponent def : focus) {
737      List<ConceptPropertyComponent> pcl = getPropertyValues(def, parent);
738      for (ConceptPropertyComponent pc : pcl) {
739        String code = pc.getValue().primitiveValue();
740        ConceptDefinitionComponent tgt = findCode(root, code);
741        if (!tgt.hasUserData(USER_DATA_CROSS_LINK)) {
742          tgt.setUserData(USER_DATA_CROSS_LINK, new ArrayList<>());
743        }
744        @SuppressWarnings("unchecked")
745        List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) tgt.getUserData(USER_DATA_CROSS_LINK);
746        children.add(def);
747      }      
748      if (def.hasConcept()) {
749        crossLinkConcepts(root, def.getConcept(), parent);
750      }
751    }
752    
753  }
754
755  public static boolean hasHierarchy(@Nonnull CodeSystem cs) {
756    for (ConceptDefinitionComponent c : cs.getConcept()) {
757      if (c.hasConcept()) {
758        return true;
759      }
760    }
761    return false;
762  }
763
764  public static void sortAllCodes(@Nonnull CodeSystem cs) {
765    sortAllCodes(cs.getConcept());
766  }
767
768  private static void sortAllCodes(List<ConceptDefinitionComponent> list) {
769    Collections.sort(list, new ConceptDefinitionComponentSorter());
770    for (ConceptDefinitionComponent cd : list) {
771      if (cd.hasConcept()) {
772        sortAllCodes(cd.getConcept());
773      }
774    }    
775  }
776
777  public static Coding readCoding(String jurisdiction) {    
778    return jurisdiction == null || !jurisdiction.contains("#") ?  null : new Coding().setCode(jurisdiction.substring(jurisdiction.indexOf("#")+1)).setSystem(jurisdiction.substring(0, jurisdiction.indexOf("#")));
779  }
780
781  public static SystemReference getSystemReference(String system, IWorkerContext ctxt) {
782    if (system == null) {
783      return null;
784    } if ("http://snomed.info/sct".equals(system)) {
785      return new SystemReference("SNOMED CT", "https://browser.ihtsdotools.org/");      
786    } else if ("http://loinc.org".equals(system)) {
787      return new SystemReference("LOINC", "https://loinc.org/");            
788    } else if ("http://unitsofmeasure.org".equals(system)) {
789      return new SystemReference("UCUM", "http://ucum.org");            
790    } else if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) {
791      return new SystemReference("RxNorm", "http://www.nlm.nih.gov/research/umls/rxnorm");
792    } else if (ctxt != null) {
793      CodeSystem cs = ctxt.fetchCodeSystem(system);
794      if (cs != null && cs.hasWebPath()) {
795        return new SystemReference(cs.present(), cs.getWebPath(), Utilities.isAbsoluteUrl(cs.getWebPath()));
796      } else if (cs != null) {
797        return new SystemReference(cs.present(), null);
798      }
799    }
800    return null;
801  }
802
803  public static boolean isNotCurrent(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) {
804    return isInactive(cs, c) || isDeprecated(cs, c, false);
805  }
806
807  public static List<String> getDisplays(@Nonnull CodeSystem srcCS, @Nonnull ConceptDefinitionComponent cd) {
808    List<String> list = new ArrayList<>();
809    if (cd.hasDisplay()) {
810      list.add(cd.getDisplay());
811    }
812    for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) {
813      if (!list.contains(d.getValue())) {
814        list.add(d.getValue());
815      }
816    }
817    return list;
818  }
819
820  public static boolean checkDisplay(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cd, @Nonnull String display) {
821    List<String> displays = getDisplays(cs, cd);
822    for (String s : displays) {
823      if (s.equalsIgnoreCase(display)) {
824        return true;
825      }
826    }
827    return false;
828  }
829
830  public static int countCodes(@Nonnull CodeSystem cs) {
831    return countCodes(cs.getConcept());
832  }
833
834  private static int countCodes(List<ConceptDefinitionComponent> concept) {
835    int t = concept.size();
836    for (ConceptDefinitionComponent cd : concept) {
837      t = t + (cd.hasConcept() ?  countCodes(cd.getConcept()) : 0);
838    }
839    return t;
840  }
841
842  public static CodeSystem mergeSupplements(@Nonnull CodeSystem cs, @Nonnull List<CodeSystem> supplements) {
843    CodeSystem ret = cs.copy();
844    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
845    for (CodeSystem sup : supplements) {
846      b.append(sup.getVersionedUrl());      
847    }
848    ret.setUserData(UserDataNames.tx_known_supplements, b.toString());
849
850    for (ConceptDefinitionComponent t : ret.getConcept()) {
851      mergeSupplements(ret, t, supplements);
852    }
853    return ret;
854  }
855
856  private static void mergeSupplements(CodeSystem ret, ConceptDefinitionComponent fdef, List<CodeSystem> supplements) {
857    for (CodeSystem cs : supplements) {
858      ConceptDefinitionComponent def = CodeSystemUtilities.findCode(cs.getConcept(), fdef.getCode());
859      if (def != null) {
860        for (Extension ext : def.getExtension()) {
861          fdef.addExtension(ext.copy());
862        }
863        for (ConceptDefinitionDesignationComponent d : def.getDesignation()) {
864          fdef.addDesignation(d.copy());
865        }
866        for (ConceptPropertyComponent p : def.getProperty()) {
867          PropertyComponent pd = CodeSystemUtilities.getPropertyDefinition(cs, p);
868          String code;
869          if (pd != null) {
870            code = defineProperty(ret, pd, propertyTypeForType(p.getValue()));
871          } else {
872            code = defineProperty(ret, p.getCode(), propertyTypeForType(p.getValue()));
873          }
874          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");
875        }
876      }
877      for (ConceptDefinitionComponent t : fdef.getConcept()) {
878        mergeSupplements(ret, t, supplements);
879      }      
880    }
881  }
882
883  private static PropertyType propertyTypeForType(DataType value) {
884    if (value == null) {
885      return PropertyType.NULL;
886    }
887    if (value instanceof CodeType) {
888      return PropertyType.CODE;
889    }
890    if (value instanceof Coding) {
891      return PropertyType.CODING;
892    }
893    if (value instanceof StringType) {
894      return PropertyType.STRING;
895    }
896    if (value instanceof IntegerType) {
897      return PropertyType.INTEGER;
898    }
899    if (value instanceof BooleanType) {
900      return PropertyType.BOOLEAN;
901    }
902    if (value instanceof DateTimeType) {
903      return PropertyType.DATETIME;
904    }
905    if (value instanceof DecimalType) {
906      return PropertyType.DECIMAL;
907    }
908    throw new FHIRException("Unsupported property value for a CodeSystem Property: "+value.fhirType());
909  }
910
911  private static String defineProperty(CodeSystem cs, PropertyComponent pd, PropertyType pt) {
912    for (PropertyComponent p : cs.getProperty()) {
913      if (p.hasCode() && p.getCode().equals(pd.getCode())) {
914        if (!p.getUri().equals(pd.getUri())) {
915          throw new Error("URI mismatch for code "+pd.getCode()+" url = "+p.getUri()+" vs "+pd.getUri());
916        }
917        if (!p.getType().equals(pt)) {
918          throw new Error("Type mismatch for code "+pd.getCode()+" type = "+p.getType().toCode()+" vs "+pt.toCode());
919        }
920        return pd.getCode();
921      }
922    }
923    cs.addProperty().setCode(pd.getCode()).setUri(pd.getUri()).setType(pt);
924    return pd.getCode();
925
926  }
927
928
929  private static PropertyComponent getPropertyDefinition(CodeSystem cs, ConceptPropertyComponent p) {
930    for (PropertyComponent t : cs.getProperty()) {
931      if (t.hasCode() && t.getCode().equals(p.getCode())) {
932        return t;
933      }
934    }
935    return null;
936  }
937
938  public static boolean hasProperties(@Nonnull CodeSystem cs) {
939    return hasProperties(cs.getConcept());
940  }
941
942  private static boolean hasProperties(List<ConceptDefinitionComponent> list) {
943    for (ConceptDefinitionComponent c : list) {
944      if (c.hasProperty() || hasProperties(c.getConcept())) {
945        return true;
946      }
947    }
948    return false;
949  }
950
951  public static boolean hasDesignations(@Nonnull CodeSystem cs) {
952    return hasDesignations(cs.getConcept());
953  }
954
955  private static boolean hasDesignations(List<ConceptDefinitionComponent> list) {
956    for (ConceptDefinitionComponent c : list) {
957      if (c.hasDesignation() || hasDesignations(c.getConcept())) {
958        return true;
959      }
960    }
961    return false;
962  }
963
964  public static boolean hasPropertyDef(@Nonnull CodeSystem cs, @Nonnull String property) {
965    
966    for (PropertyComponent pd : cs.getProperty()) {
967      if (pd.hasCode() && pd.getCode().equals(property)) {
968        return true;
969      }
970    }
971    return false;
972  }
973
974  public static DataType getProperty(@Nonnull CodeSystem cs, @Nonnull String code, @Nonnull String property) {
975    ConceptDefinitionComponent def = getCode(cs, code);
976    return getProperty(cs, def, property);
977  }
978  
979  public static DataType getProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def, @Nonnull String property) {
980    PropertyComponent defn = getPropertyDefinition(cs, property);
981    if (defn != null) {
982      property = defn.getCode();
983    }
984    ConceptPropertyComponent cp = getProperty(def, property);
985    return cp == null ? null : cp.getValue();
986  }
987
988  public static boolean hasMarkdownInDefinitions(@Nonnull CodeSystem cs, @Nonnull MarkDownProcessor md) {
989    return hasMarkdownInDefinitions(cs.getConcept(), md);
990  }
991
992  private static boolean hasMarkdownInDefinitions(List<ConceptDefinitionComponent> concepts, MarkDownProcessor md) {
993    for (ConceptDefinitionComponent c : concepts) {
994      if (c.hasDefinition() && md.isProbablyMarkdown(c.getDefinition(), true)) {
995        return true;
996      }
997      if (c.hasConcept() && hasMarkdownInDefinitions(c.getConcept(), md)) {
998        return true;
999      }
1000    }
1001    return false;
1002  }
1003
1004  public static String getStatus(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cc) {
1005    StandardsStatus ss = ExtensionUtilities.getStandardsStatus(cc);
1006    if (ss == StandardsStatus.DEPRECATED || ss == StandardsStatus.WITHDRAWN) {
1007      return ss.toCode();
1008    }
1009    DataType v = getProperty(cs, cc, "status");
1010    if (v == null || !v.isPrimitive()) {
1011      return null;
1012    } else {
1013      return v.primitiveValue();
1014    }
1015  }
1016
1017  public static Boolean subsumes(@Nonnull CodeSystem cs, @Nonnull String pc, @Nonnull String cc) {
1018    if (pc.equals(cc)) {
1019      return true;
1020    }
1021    List<ConceptDefinitionComponent> child = findCodeWithParents(null, cs.getConcept(), cc);
1022    for (ConceptDefinitionComponent item : child) {
1023      if (pc.equals(item.getCode())) {
1024        return true;
1025      }
1026    }
1027    return false;
1028  }
1029
1030  public static Set<String> codes(@Nonnull CodeSystem cs) {
1031    Set<String> res = new HashSet<>();
1032    addCodes(res, cs.getConcept());
1033    return res;
1034  }
1035
1036  private static void addCodes(Set<String> res, List<ConceptDefinitionComponent> list) {
1037    for (ConceptDefinitionComponent cd : list) {
1038      if (cd.hasCode()) {
1039        res.add(cd.getCode());
1040      }
1041      if (cd.hasConcept()) {
1042        addCodes(res, cd.getConcept());
1043      }
1044    }    
1045  }
1046  
1047  /**
1048   * property in this case is the name of a property that appears in a ValueSet filter 
1049   * 
1050   * @param cs
1051   * @param property
1052   * @return
1053   */
1054  public static PropertyComponent getPropertyDefinition(@Nonnull CodeSystem cs, @Nonnull String property) {
1055    String uri = getStandardPropertyUri(property);
1056    if (uri != null) {
1057      for (PropertyComponent cp : cs.getProperty()) {
1058        if (uri.equals(cp.getUri())) {
1059          return cp;
1060        }
1061      }
1062    }
1063    for (PropertyComponent cp : cs.getProperty()) {
1064      if (cp.getCode().equals(property)) {
1065        return cp;
1066      }
1067    }
1068    return null;
1069  }
1070
1071  public static boolean isDefinedProperty(@Nonnull CodeSystem cs, @Nonnull String property) {
1072    String uri = getStandardPropertyUri(property);
1073    if (uri != null) {
1074      for (PropertyComponent cp : cs.getProperty()) {
1075        if (uri.equals(cp.getUri())) {
1076          return true;
1077        }
1078      }
1079    }
1080    for (PropertyComponent cp : cs.getProperty()) {
1081      if (cp.getCode().equals(property) && (uri == null || !cp.hasUri())) { // if uri is right, will return from above
1082        return true;
1083      }
1084    }
1085    return false;
1086  }
1087  
1088
1089  private static String getStandardPropertyUri(String property) {
1090    switch (property) {
1091    case "status" : return "http://hl7.org/fhir/concept-properties#status";
1092    case "inactive" : return "http://hl7.org/fhir/concept-properties#inactive";
1093    case "effectiveDate" : return "http://hl7.org/fhir/concept-properties#effectiveDate";
1094    case "deprecationDate" : return "http://hl7.org/fhir/concept-properties#deprecationDate";
1095    case "retirementDate" : return "http://hl7.org/fhir/concept-properties#retirementDate";
1096    case "notSelectable" : return "http://hl7.org/fhir/concept-properties#notSelectable";
1097    case "parent" : return "http://hl7.org/fhir/concept-properties#parent";
1098    case "child" : return "http://hl7.org/fhir/concept-properties#child";
1099    case "partOf" : return "http://hl7.org/fhir/concept-properties#partOf";
1100    case "synonym" : return "http://hl7.org/fhir/concept-properties#synonym";
1101    case "comment" : return "http://hl7.org/fhir/concept-properties#comment";
1102    case "itemWeight" : return "http://hl7.org/fhir/concept-properties#itemWeight";        
1103    }
1104    return null;
1105  }
1106
1107  public static boolean isExemptFromMultipleVersionChecking(String url) {
1108    return Utilities.existsInList(url, "http://snomed.info/sct", "http://loinc.org");
1109  }
1110
1111  public static PropertyComponent getPropertyByUri(@Nonnull CodeSystem cs, @Nonnull String uri) {
1112    for (PropertyComponent t : cs.getProperty()) {
1113      if (uri.equals(t.getUri())) {
1114        return t;
1115      }
1116    }
1117    return null;
1118  }
1119
1120  public static CodeSystem convertSD(@Nonnull StructureDefinition sd) {
1121    CodeSystem cs = new CodeSystem();
1122    cs.setId(sd.getId());
1123    cs.setUrl(sd.getUrl());
1124    cs.setVersion(sd.getVersion());
1125    cs.setStatus(sd.getStatus());
1126    cs.setContent(Enumerations.CodeSystemContentMode.COMPLETE);
1127    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
1128      ConceptDefinitionComponent cd = cs.addConcept();
1129      cd.setCode(ed.getId());
1130      cd.setDisplay(ed.getId());
1131      ed.setDefinition(ed.getDefinition());
1132    }
1133    return cs;
1134  }
1135
1136  public static boolean hasCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) {
1137    return hasProperty(cs, c, "http://hl7.org/fhir/concept-properties#comments", "comments");
1138  }
1139
1140
1141  public static String getCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) {
1142    return getProperty(cs, c, "http://hl7.org/fhir/concept-properties#comments", "comments");
1143  }
1144
1145  public static void addCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cc, @Nonnull String comments) {
1146    String code = "comments";
1147
1148    for (PropertyComponent p : cs.getProperty()) {
1149      if ("http://hl7.org/fhir/concept-properties#comments".equals(p.getUri())) {
1150        code = p.getCode();
1151      }
1152    }
1153    setProperty(cs, cc, code, new StringType(comments));
1154  }
1155
1156}
1157