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