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.DEPRECATED || ss == StandardsStatus.WITHDRAWN) {
418      return true;
419    }
420    for (ConceptPropertyComponent p : def.getProperty()) {
421      if ("status".equals(p.getCode()) && p.hasValueStringType()) {
422        return "inactive".equals(p.getValueStringType().primitiveValue()) || "retired".equals(p.getValueStringType().primitiveValue()) || "deprecated".equals(p.getValueStringType().primitiveValue());
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 CodeSystem makeShareable(@Nonnull CodeSystem cs) {
470    if (!cs.hasExperimental()) {
471      cs.setExperimental(false);
472    }
473
474    if (!cs.hasMeta())
475      cs.setMeta(new Meta());
476    for (UriType t : cs.getMeta().getProfile()) 
477      if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue()))
478        return cs;
479    cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
480    return cs;
481  }
482
483  public static boolean checkMakeShareable(@Nonnull CodeSystem cs) {
484    boolean changed = false;
485    if (!cs.hasExperimental()) {
486      cs.setExperimental(false);
487      changed = true;
488    }
489
490    if (!cs.hasMeta())
491      cs.setMeta(new Meta());
492    for (UriType t : cs.getMeta().getProfile()) 
493      if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue()))
494        return changed;
495    cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
496    return true;
497  }
498
499  public static void setOID(@Nonnull CodeSystem cs, @Nonnull String oid) {
500    if (!oid.startsWith("urn:oid:"))
501       oid = "urn:oid:" + oid;
502    if (!cs.hasIdentifier())
503      cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid));
504    else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
505      cs.getIdentifierFirstRep().setValue(oid);
506    else
507      throw new Error("unable to set OID on code system");
508    
509  }
510
511  public static boolean hasOID(@Nonnull CanonicalResource cs) {
512    return getOID(cs) != null;
513  }
514
515  public static String getOID(@Nonnull CanonicalResource cs) {
516    if (cs != null && cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
517        return cs.getIdentifierFirstRep().getValue().substring(8);
518    return null;
519  }
520
521  public static ConceptDefinitionComponent findCode(@Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code) {
522    for (ConceptDefinitionComponent c : list) {
523      if (c.hasCode() && c.getCode().equals(code))
524        return c;
525      ConceptDefinitionComponent s = findCode(c.getConcept(), code);
526      if (s != null)
527        return s;
528    }
529    return null;
530  }
531
532
533  public static List<ConceptDefinitionComponent> findCodeWithParents(@Nonnull List<ConceptDefinitionComponent> parents, @Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code) {
534    for (ConceptDefinitionComponent c : list) {
535      if (c.hasCode() && c.getCode().equals(code)) {
536        return addToList(parents, c);
537      }
538      List<ConceptDefinitionComponent> s = findCodeWithParents(addToList(parents, c), c.getConcept(), code);
539      if (s != null)
540        return s;
541    }
542    return null;
543  }
544
545  private static List<ConceptDefinitionComponent> addToList(List<ConceptDefinitionComponent> parents, ConceptDefinitionComponent c) {
546    List<ConceptDefinitionComponent> res = new ArrayList<CodeSystem.ConceptDefinitionComponent>();
547    if (parents != null) {
548      res.addAll(parents);
549    }
550    res.add(c);
551    return res;
552  }
553
554  public static ConceptDefinitionComponent findCodeOrAltCode(@Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code, @Nonnull String use) {
555    for (ConceptDefinitionComponent c : list) {
556      if (c.hasCode() && c.getCode().equals(code))
557        return c;
558      for (ConceptPropertyComponent p : c.getProperty()) {
559        if ("alternateCode".equals(p.getCode()) && (use == null || hasUse(p, use)) && p.hasValue() && p.getValue().isPrimitive() && code.equals(p.getValue().primitiveValue())) {
560          return c;
561        }
562      }
563      ConceptDefinitionComponent s = findCodeOrAltCode(c.getConcept(), code, use);
564      if (s != null)
565        return s;
566    }
567    return null;
568  }
569
570  private static boolean hasUse(ConceptPropertyComponent p, String use) {
571    for (Extension ext : p.getExtensionsByUrl(ExtensionDefinitions.EXT_CS_ALTERNATE_USE)) {
572      if (ext.hasValueCoding() && use.equals(ext.getValueCoding().getCode())) {
573        return true;
574      }
575    }
576    return false;
577  }
578
579  public static void markStatus(@Nonnull CodeSystem cs, String wg, StandardsStatus status, String pckage, String fmm, String normativeVersion) throws FHIRException {
580    if (wg != null) {
581      if (!ExtensionUtilities.hasExtension(cs, ExtensionDefinitions.EXT_WORKGROUP) || 
582          (Utilities.existsInList(ExtensionUtilities.readStringExtension(cs, ExtensionDefinitions.EXT_WORKGROUP), "fhir", "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) {
583        CanonicalResourceUtilities.setHl7WG(cs, wg);
584      }
585    }
586    if (status != null) {
587      StandardsStatus ss = ExtensionUtilities.getStandardsStatus(cs);
588      if (ss == null || ss.isLowerThan(status)) 
589        ExtensionUtilities.setStandardsStatus(cs, status, normativeVersion);
590      if (pckage != null) {
591        if (!cs.hasUserData(UserDataNames.kindling_ballot_package))
592          cs.setUserData(UserDataNames.kindling_ballot_package, pckage);
593        else if (!pckage.equals(cs.getUserString(UserDataNames.kindling_ballot_package)))
594          if (!"infrastructure".equals(cs.getUserString(UserDataNames.kindling_ballot_package)))
595            log.warn("Code System "+cs.getUrl()+": ownership clash "+pckage+" vs "+cs.getUserString(UserDataNames.kindling_ballot_package));
596      }
597      if (status == StandardsStatus.NORMATIVE) {
598        cs.setStatus(PublicationStatus.ACTIVE);
599      }
600    }
601    if (fmm != null) {
602      String sfmm = ExtensionUtilities.readStringExtension(cs, ExtensionDefinitions.EXT_FMM_LEVEL);
603      if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 
604        ExtensionUtilities.setIntegerExtension(cs, ExtensionDefinitions.EXT_FMM_LEVEL, Integer.parseInt(fmm));
605      }
606    }
607  }
608
609 
610  public static DataType readProperty(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) {
611    for (ConceptPropertyComponent p : concept.getProperty())
612      if (p.hasCode() && p.getCode().equals(code))
613        return p.getValue(); 
614    return null;
615  }
616
617  public static ConceptPropertyComponent getProperty(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) {
618    for (ConceptPropertyComponent p : concept.getProperty())
619      if (p.hasCode() && p.getCode().equals(code))
620        return p; 
621    return null;
622  }
623  
624  public static List<ConceptPropertyComponent> getPropertyValues(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) {
625    List<ConceptPropertyComponent> res = new ArrayList<>();
626    if (code != null) {
627      for (ConceptPropertyComponent p : concept.getProperty()) {
628        if (code.equals(p.getCode())) {
629          res.add(p); 
630        }
631      }
632    }
633    return res;
634  }
635
636  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
637  // returns additional parents not in the hierarchy
638  public static List<String> getOtherChildren(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) {
639    List<String> res = new ArrayList<String>();
640    for (ConceptPropertyComponent p : c.getProperty()) {
641      if ("parent".equals(p.getCode())) {
642        res.add(p.getValue().primitiveValue());
643      }
644    }
645    return res;
646  }
647
648  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
649  public static void addOtherChild(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent owner, @Nonnull String code) {
650    defineChildProperty(cs);
651    owner.addProperty().setCode("child").setValue(new CodeType(code));
652  }
653
654  public static boolean hasProperty(@Nonnull ConceptDefinitionComponent c, @Nonnull String code) {
655    for (ConceptPropertyComponent cp : c.getProperty()) {
656      if (code.equals(cp.getCode())) {
657        return true;
658      }
659    }
660    return false;
661  }
662
663  public static String getProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c, @Nonnull String uri, @Nonnull String code) {
664    for (ConceptPropertyComponent cp : c.getProperty()) {
665      if (code.equals(cp.getCode())) {
666        return cp.getValue().primitiveValue();
667      }
668    }
669    for (PropertyComponent p : cs.getProperty()) {
670      if (uri.equals(p.getUri())) {
671        var t = getProperty(c, p.getCode());
672        if (t != null) {
673          return t.primitiveValue();
674        }
675      }
676    }
677    return null;
678  }
679
680  public static boolean hasProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c, @Nonnull String uri, @Nonnull String code) {
681    for (ConceptPropertyComponent cp : c.getProperty()) {
682      if (code.equals(cp.getCode())) {
683        return true;
684      }
685    }
686    for (PropertyComponent p : cs.getProperty()) {
687      if (uri.equals(p.getUri())) {
688        return hasProperty(c, p.getCode());
689      }
690    }
691    return false;
692  }
693
694  public static boolean hasCode(@Nonnull CodeSystem cs, @Nonnull String code) {
695    for (ConceptDefinitionComponent cc : cs.getConcept()) {
696      if (hasCode(cc, code)) {
697        return true;
698      }
699    }
700    return false;
701  }
702
703  private static boolean hasCode(@Nonnull ConceptDefinitionComponent cc, @Nonnull String code) {
704    if (code.equals(cc.getCode())) {
705      return true;
706    }
707    for (ConceptDefinitionComponent c : cc.getConcept()) {
708      if (hasCode(c, code)) {
709        return true;
710      }
711    }
712    return false;
713  }
714
715  public static ConceptDefinitionComponent getCode(@Nonnull CodeSystem cs, @Nonnull String code) {
716    for (ConceptDefinitionComponent cc : cs.getConcept()) {
717      ConceptDefinitionComponent cd = getCode(cc, code);
718      if (cd != null) {
719        return cd;
720      }
721    }
722    return null;
723  }
724
725  private static ConceptDefinitionComponent getCode(@Nonnull ConceptDefinitionComponent cc, @Nonnull String code) {
726    if (code.equals(cc.getCode())) {
727      return cc;
728    }
729    for (ConceptDefinitionComponent c : cc.getConcept()) {
730      ConceptDefinitionComponent cd = getCode(c, code);
731      if (cd != null) {
732        return cd;
733      }
734    }
735    return null;
736  }
737
738  public static void crossLinkCodeSystem(@Nonnull CodeSystem cs) {
739    String parent = getPropertyByUrl(cs, "http://hl7.org/fhir/concept-properties#parent");
740    if ((parent != null)) {
741      crossLinkConcepts(cs.getConcept(), cs.getConcept(), parent);
742    }
743  }
744
745  private static String getPropertyByUrl(@Nonnull CodeSystem cs, String url) {
746    for (PropertyComponent pc : cs.getProperty()) {
747      if (url.equals(pc.getUri())) {
748        return pc.getCode();
749      }
750    }
751    return null;
752  }
753
754  private static void crossLinkConcepts(List<ConceptDefinitionComponent> root, List<ConceptDefinitionComponent> focus, String parent) {
755    for (ConceptDefinitionComponent def : focus) {
756      List<ConceptPropertyComponent> pcl = getPropertyValues(def, parent);
757      for (ConceptPropertyComponent pc : pcl) {
758        String code = pc.getValue().primitiveValue();
759        ConceptDefinitionComponent tgt = findCode(root, code);
760        if (!tgt.hasUserData(USER_DATA_CROSS_LINK)) {
761          tgt.setUserData(USER_DATA_CROSS_LINK, new ArrayList<>());
762        }
763        @SuppressWarnings("unchecked")
764        List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) tgt.getUserData(USER_DATA_CROSS_LINK);
765        children.add(def);
766      }      
767      if (def.hasConcept()) {
768        crossLinkConcepts(root, def.getConcept(), parent);
769      }
770    }
771    
772  }
773
774  public static boolean hasHierarchy(@Nonnull CodeSystem cs) {
775    for (ConceptDefinitionComponent c : cs.getConcept()) {
776      if (c.hasConcept()) {
777        return true;
778      }
779    }
780    return false;
781  }
782
783  public static void sortAllCodes(@Nonnull CodeSystem cs) {
784    sortAllCodes(cs.getConcept());
785  }
786
787  private static void sortAllCodes(List<ConceptDefinitionComponent> list) {
788    Collections.sort(list, new ConceptDefinitionComponentSorter());
789    for (ConceptDefinitionComponent cd : list) {
790      if (cd.hasConcept()) {
791        sortAllCodes(cd.getConcept());
792      }
793    }    
794  }
795
796  public static Coding readCoding(String jurisdiction) {    
797    return jurisdiction == null || !jurisdiction.contains("#") ?  null : new Coding().setCode(jurisdiction.substring(jurisdiction.indexOf("#")+1)).setSystem(jurisdiction.substring(0, jurisdiction.indexOf("#")));
798  }
799
800  public static SystemReference getSystemReference(String system, IWorkerContext ctxt) {
801    if (system == null) {
802      return null;
803    } if ("http://snomed.info/sct".equals(system)) {
804      return new SystemReference("SNOMED CT", "https://browser.ihtsdotools.org/");      
805    } else if ("http://loinc.org".equals(system)) {
806      return new SystemReference("LOINC", "https://loinc.org/");            
807    } else if ("http://unitsofmeasure.org".equals(system)) {
808      return new SystemReference("UCUM", "http://ucum.org");            
809    } else if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) {
810      return new SystemReference("RxNorm", "http://www.nlm.nih.gov/research/umls/rxnorm");
811    } else if (ctxt != null) {
812      CodeSystem cs = ctxt.fetchCodeSystem(system);
813      if (cs != null && cs.hasWebPath()) {
814        return new SystemReference(cs.present(), cs.getWebPath(), Utilities.isAbsoluteUrl(cs.getWebPath()));
815      } else if (cs != null) {
816        return new SystemReference(cs.present(), null);
817      }
818    }
819    return null;
820  }
821
822  public static boolean isNotCurrent(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) {
823    return isInactive(cs, c) || isDeprecated(cs, c, false);
824  }
825
826  public static List<String> getDisplays(@Nonnull CodeSystem srcCS, @Nonnull ConceptDefinitionComponent cd) {
827    List<String> list = new ArrayList<>();
828    if (cd.hasDisplay()) {
829      list.add(cd.getDisplay());
830    }
831    for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) {
832      if (!list.contains(d.getValue())) {
833        list.add(d.getValue());
834      }
835    }
836    return list;
837  }
838
839  public static boolean checkDisplay(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cd, @Nonnull String display) {
840    List<String> displays = getDisplays(cs, cd);
841    for (String s : displays) {
842      if (s.equalsIgnoreCase(display)) {
843        return true;
844      }
845    }
846    return false;
847  }
848
849  public static int countCodes(@Nonnull CodeSystem cs) {
850    return countCodes(cs.getConcept());
851  }
852
853  private static int countCodes(List<ConceptDefinitionComponent> concept) {
854    int t = concept.size();
855    for (ConceptDefinitionComponent cd : concept) {
856      t = t + (cd.hasConcept() ?  countCodes(cd.getConcept()) : 0);
857    }
858    return t;
859  }
860
861  public static CodeSystem mergeSupplements(@Nonnull CodeSystem cs, @Nonnull List<CodeSystem> supplements) {
862    CodeSystem ret = cs.copy();
863    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
864    for (CodeSystem sup : supplements) {
865      b.append(sup.getVersionedUrl());      
866    }
867    ret.setUserData(UserDataNames.tx_known_supplements, b.toString());
868
869    for (ConceptDefinitionComponent t : ret.getConcept()) {
870      mergeSupplements(ret, t, supplements);
871    }
872    return ret;
873  }
874
875  private static void mergeSupplements(CodeSystem ret, ConceptDefinitionComponent fdef, List<CodeSystem> supplements) {
876    for (CodeSystem cs : supplements) {
877      ConceptDefinitionComponent def = CodeSystemUtilities.findCode(cs.getConcept(), fdef.getCode());
878      if (def != null) {
879        for (Extension ext : def.getExtension()) {
880          fdef.addExtension(ext.copy());
881        }
882        for (ConceptDefinitionDesignationComponent d : def.getDesignation()) {
883          fdef.addDesignation(d.copy());
884        }
885        for (ConceptPropertyComponent p : def.getProperty()) {
886          PropertyComponent pd = CodeSystemUtilities.getPropertyDefinition(cs, p);
887          String code;
888          if (pd != null) {
889            code = defineProperty(ret, pd, propertyTypeForType(p.getValue()));
890          } else {
891            code = defineProperty(ret, p.getCode(), propertyTypeForType(p.getValue()));
892          }
893          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");
894        }
895      }
896      for (ConceptDefinitionComponent t : fdef.getConcept()) {
897        mergeSupplements(ret, t, supplements);
898      }      
899    }
900  }
901
902  private static PropertyType propertyTypeForType(DataType value) {
903    if (value == null) {
904      return PropertyType.NULL;
905    }
906    if (value instanceof CodeType) {
907      return PropertyType.CODE;
908    }
909    if (value instanceof CodeType) {
910      return PropertyType.CODING;
911    }
912    if (value instanceof CodeType) {
913      return PropertyType.STRING;
914    }
915    if (value instanceof CodeType) {
916      return PropertyType.INTEGER;
917    }
918    if (value instanceof CodeType) {
919      return PropertyType.BOOLEAN;
920    }
921    if (value instanceof CodeType) {
922      return PropertyType.DATETIME;
923    }
924    if (value instanceof CodeType) {
925      return PropertyType.DECIMAL;
926    }
927    throw new FHIRException("Unsupported property value for a CodeSystem Property: "+value.fhirType());
928  }
929
930  private static String defineProperty(CodeSystem cs, PropertyComponent pd, PropertyType pt) {
931    for (PropertyComponent p : cs.getProperty()) {
932      if (p.hasCode() && p.getCode().equals(pd.getCode())) {
933        if (!p.getUri().equals(pd.getUri())) {
934          throw new Error("URI mismatch for code "+pd.getCode()+" url = "+p.getUri()+" vs "+pd.getUri());
935        }
936        if (!p.getType().equals(pt)) {
937          throw new Error("Type mismatch for code "+pd.getCode()+" type = "+p.getType().toCode()+" vs "+pt.toCode());
938        }
939        return pd.getCode();
940      }
941    }
942    cs.addProperty().setCode(pd.getCode()).setUri(pd.getUri()).setType(pt);
943    return pd.getCode();
944
945  }
946
947
948  private static PropertyComponent getPropertyDefinition(CodeSystem cs, ConceptPropertyComponent p) {
949    for (PropertyComponent t : cs.getProperty()) {
950      if (t.hasCode() && t.getCode().equals(p.getCode())) {
951        return t;
952      }
953    }
954    return null;
955  }
956
957  public static boolean hasProperties(@Nonnull CodeSystem cs) {
958    return hasProperties(cs.getConcept());
959  }
960
961  private static boolean hasProperties(List<ConceptDefinitionComponent> list) {
962    for (ConceptDefinitionComponent c : list) {
963      if (c.hasProperty() || hasProperties(c.getConcept())) {
964        return true;
965      }
966    }
967    return false;
968  }
969
970  public static boolean hasDesignations(@Nonnull CodeSystem cs) {
971    return hasDesignations(cs.getConcept());
972  }
973
974  private static boolean hasDesignations(List<ConceptDefinitionComponent> list) {
975    for (ConceptDefinitionComponent c : list) {
976      if (c.hasDesignation() || hasDesignations(c.getConcept())) {
977        return true;
978      }
979    }
980    return false;
981  }
982
983  public static boolean hasPropertyDef(@Nonnull CodeSystem cs, @Nonnull String property) {
984    
985    for (PropertyComponent pd : cs.getProperty()) {
986      if (pd.hasCode() && pd.getCode().equals(property)) {
987        return true;
988      }
989    }
990    return false;
991  }
992
993  public static DataType getProperty(@Nonnull CodeSystem cs, @Nonnull String code, @Nonnull String property) {
994    ConceptDefinitionComponent def = getCode(cs, code);
995    return getProperty(cs, def, property);
996  }
997  
998  public static DataType getProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def, @Nonnull String property) {
999    PropertyComponent defn = getPropertyDefinition(cs, property);
1000    if (defn != null) {
1001      property = defn.getCode();
1002    }
1003    ConceptPropertyComponent cp = getProperty(def, property);
1004    return cp == null ? null : cp.getValue();
1005  }
1006
1007  public static boolean hasMarkdownInDefinitions(@Nonnull CodeSystem cs, @Nonnull MarkDownProcessor md) {
1008    return hasMarkdownInDefinitions(cs.getConcept(), md);
1009  }
1010
1011  private static boolean hasMarkdownInDefinitions(List<ConceptDefinitionComponent> concepts, MarkDownProcessor md) {
1012    for (ConceptDefinitionComponent c : concepts) {
1013      if (c.hasDefinition() && md.isProbablyMarkdown(c.getDefinition(), true)) {
1014        return true;
1015      }
1016      if (c.hasConcept() && hasMarkdownInDefinitions(c.getConcept(), md)) {
1017        return true;
1018      }
1019    }
1020    return false;
1021  }
1022
1023  public static String getStatus(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cc) {
1024    StandardsStatus ss = ExtensionUtilities.getStandardsStatus(cc);
1025    if (ss == StandardsStatus.DEPRECATED || ss == StandardsStatus.WITHDRAWN) {
1026      return ss.toCode();
1027    }
1028    DataType v = getProperty(cs, cc, "status");
1029    if (v == null || !v.isPrimitive()) {
1030      return null;
1031    } else {
1032      return v.primitiveValue();
1033    }
1034  }
1035
1036  public static Boolean subsumes(@Nonnull CodeSystem cs, @Nonnull String pc, @Nonnull String cc) {
1037    if (pc.equals(cc)) {
1038      return true;
1039    }
1040    List<ConceptDefinitionComponent> child = findCodeWithParents(null, cs.getConcept(), cc);
1041    for (ConceptDefinitionComponent item : child) {
1042      if (pc.equals(item.getCode())) {
1043        return true;
1044      }
1045    }
1046    return false;
1047  }
1048
1049  public static Set<String> codes(@Nonnull CodeSystem cs) {
1050    Set<String> res = new HashSet<>();
1051    addCodes(res, cs.getConcept());
1052    return res;
1053  }
1054
1055  private static void addCodes(Set<String> res, List<ConceptDefinitionComponent> list) {
1056    for (ConceptDefinitionComponent cd : list) {
1057      if (cd.hasCode()) {
1058        res.add(cd.getCode());
1059      }
1060      if (cd.hasConcept()) {
1061        addCodes(res, cd.getConcept());
1062      }
1063    }    
1064  }
1065  
1066  /**
1067   * property in this case is the name of a property that appears in a ValueSet filter 
1068   * 
1069   * @param cs
1070   * @param property
1071   * @return
1072   */
1073  public static PropertyComponent getPropertyDefinition(@Nonnull CodeSystem cs, @Nonnull String property) {
1074    String uri = getStandardPropertyUri(property);
1075    if (uri != null) {
1076      for (PropertyComponent cp : cs.getProperty()) {
1077        if (uri.equals(cp.getUri())) {
1078          return cp;
1079        }
1080      }
1081    }
1082    for (PropertyComponent cp : cs.getProperty()) {
1083      if (cp.getCode().equals(property)) {
1084        return cp;
1085      }
1086    }
1087    return null;
1088  }
1089
1090  public static boolean isDefinedProperty(@Nonnull CodeSystem cs, @Nonnull String property) {
1091    String uri = getStandardPropertyUri(property);
1092    if (uri != null) {
1093      for (PropertyComponent cp : cs.getProperty()) {
1094        if (uri.equals(cp.getUri())) {
1095          return true;
1096        }
1097      }
1098    }
1099    for (PropertyComponent cp : cs.getProperty()) {
1100      if (cp.getCode().equals(property) && (uri == null || !cp.hasUri())) { // if uri is right, will return from above
1101        return true;
1102      }
1103    }
1104    return false;
1105  }
1106  
1107
1108  private static String getStandardPropertyUri(String property) {
1109    switch (property) {
1110    case "status" : return "http://hl7.org/fhir/concept-properties#status";
1111    case "inactive" : return "http://hl7.org/fhir/concept-properties#inactive";
1112    case "effectiveDate" : return "http://hl7.org/fhir/concept-properties#effectiveDate";
1113    case "deprecationDate" : return "http://hl7.org/fhir/concept-properties#deprecationDate";
1114    case "retirementDate" : return "http://hl7.org/fhir/concept-properties#retirementDate";
1115    case "notSelectable" : return "http://hl7.org/fhir/concept-properties#notSelectable";
1116    case "parent" : return "http://hl7.org/fhir/concept-properties#parent";
1117    case "child" : return "http://hl7.org/fhir/concept-properties#child";
1118    case "partOf" : return "http://hl7.org/fhir/concept-properties#partOf";
1119    case "synonym" : return "http://hl7.org/fhir/concept-properties#synonym";
1120    case "comment" : return "http://hl7.org/fhir/concept-properties#comment";
1121    case "itemWeight" : return "http://hl7.org/fhir/concept-properties#itemWeight";        
1122    }
1123    return null;
1124  }
1125
1126  public static boolean isExemptFromMultipleVersionChecking(String url) {
1127    return Utilities.existsInList(url, "http://snomed.info/sct", "http://loinc.org");
1128  }
1129
1130  public static PropertyComponent getPropertyByUri(@Nonnull CodeSystem cs, @Nonnull String uri) {
1131    for (PropertyComponent t : cs.getProperty()) {
1132      if (uri.equals(t.getUri())) {
1133        return t;
1134      }
1135    }
1136    return null;
1137  }
1138
1139  public static CodeSystem convertSD(@Nonnull StructureDefinition sd) {
1140    CodeSystem cs = new CodeSystem();
1141    cs.setId(sd.getId());
1142    cs.setUrl(sd.getUrl());
1143    cs.setVersion(sd.getVersion());
1144    cs.setStatus(sd.getStatus());
1145    cs.setContent(Enumerations.CodeSystemContentMode.COMPLETE);
1146    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
1147      ConceptDefinitionComponent cd = cs.addConcept();
1148      cd.setCode(ed.getId());
1149      cd.setDisplay(ed.getId());
1150      ed.setDefinition(ed.getDefinition());
1151    }
1152    return cs;
1153  }
1154
1155  public static boolean hasCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) {
1156    return hasProperty(cs, c, "http://hl7.org/fhir/concept-properties#comments", "comments");
1157  }
1158
1159
1160  public static String getCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) {
1161    return getProperty(cs, c, "http://hl7.org/fhir/concept-properties#comments", "comments");
1162  }
1163
1164  public static void addCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cc, @Nonnull String comments) {
1165    String code = "comments";
1166
1167    for (PropertyComponent p : cs.getProperty()) {
1168      if ("http://hl7.org/fhir/concept-properties#comments".equals(p.getUri())) {
1169        code = p.getCode();
1170      }
1171    }
1172    setProperty(cs, cc, code, new StringType(comments));
1173  }
1174
1175}
1176