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