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