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