001package org.hl7.fhir.r5.terminologies; 002 003import java.util.Calendar; 004import java.util.Collections; 005import java.util.Comparator; 006import java.util.HashSet; 007import java.util.List; 008import java.util.Set; 009 010/* 011 Copyright (c) 2011+, HL7, Inc. 012 All rights reserved. 013 014 Redistribution and use in source and binary forms, with or without modification, 015 are permitted provided that the following conditions are met: 016 017 * Redistributions of source code must retain the above copyright notice, this 018 list of conditions and the following disclaimer. 019 * Redistributions in binary form must reproduce the above copyright notice, 020 this list of conditions and the following disclaimer in the documentation 021 and/or other materials provided with the distribution. 022 * Neither the name of HL7 nor the names of its contributors may be used to 023 endorse or promote products derived from this software without specific 024 prior written permission. 025 026 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 027 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 028 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 029 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 030 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 031 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 032 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 033 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 034 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 035 POSSIBILITY OF SUCH DAMAGE. 036 037 */ 038 039 040import org.hl7.fhir.exceptions.FHIRException; 041import org.hl7.fhir.exceptions.FHIRFormatError; 042import org.hl7.fhir.r5.context.IWorkerContext; 043import org.hl7.fhir.r5.model.BooleanType; 044import org.hl7.fhir.r5.model.CanonicalType; 045import org.hl7.fhir.r5.model.CodeSystem; 046import org.hl7.fhir.r5.model.DateTimeType; 047import org.hl7.fhir.r5.model.StringType; 048import org.hl7.fhir.r5.model.IntegerType; 049import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 050import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 051import org.hl7.fhir.r5.model.Identifier; 052import org.hl7.fhir.r5.model.Meta; 053import org.hl7.fhir.r5.model.UriType; 054import org.hl7.fhir.r5.model.ValueSet; 055import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 056import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 057import org.hl7.fhir.r5.model.CodeType; 058import org.hl7.fhir.r5.model.Coding; 059import org.hl7.fhir.r5.model.DataType; 060import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 061import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 062import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 063import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 064import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 065import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; 066import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter; 067import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus; 068import org.hl7.fhir.r5.utils.CanonicalResourceUtilities; 069import org.hl7.fhir.r5.utils.ToolingExtensions; 070import org.hl7.fhir.utilities.StandardsStatus; 071import org.hl7.fhir.utilities.Utilities; 072import org.hl7.fhir.utilities.VersionUtilities; 073 074public class ValueSetUtilities extends TerminologyUtilities { 075 076 077 public static class ValueSetSorter implements Comparator<ValueSet> { 078 079 @Override 080 public int compare(ValueSet o1, ValueSet o2) { 081 String url1 = o1.getUrl(); 082 String url2 = o2.getUrl(); 083 int c = compareString(url1, url2); 084 if (c == 0) { 085 String ver1 = o1.getVersion(); 086 String ver2 = o2.getVersion(); 087 c = VersionUtilities.compareVersions(ver1, ver2); 088 if (c == 0) { 089 String d1 = o1.getDateElement().asStringValue(); 090 String d2 = o2.getDateElement().asStringValue(); 091 c = compareString(url1, url2); 092 } 093 } 094 return c; 095 } 096 097 private int compareString(String s1, String s2) { 098 if (s1 == null) { 099 return s2 == null ? 0 : 1; 100 } else { 101 return s1.compareTo(s2); 102 } 103 } 104 105 } 106 107 108 public static boolean isServerSide(String url) { 109 return Utilities.existsInList(url, "http://hl7.org/fhir/sid/cvx"); 110 } 111 112 public static ValueSet makeShareable(ValueSet vs) { 113 if (!vs.hasExperimental()) { 114 vs.setExperimental(false); 115 } 116 if (!vs.hasMeta()) 117 vs.setMeta(new Meta()); 118 for (UriType t : vs.getMeta().getProfile()) 119 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 120 return vs; 121 vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset")); 122 return vs; 123 } 124 125 public static boolean makeVSShareable(ValueSet vs) { 126 if (!vs.hasMeta()) 127 vs.setMeta(new Meta()); 128 for (UriType t : vs.getMeta().getProfile()) 129 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 130 return false; 131 vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset")); 132 return true; 133 } 134 135 public static void checkShareable(ValueSet vs) { 136 if (!vs.hasMeta()) 137 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 138 for (UriType t : vs.getMeta().getProfile()) { 139 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 140 return; 141 } 142 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 143 } 144 145 public static boolean hasOID(ValueSet vs) { 146 return getOID(vs) != null; 147 } 148 149 public static String getOID(ValueSet vs) { 150 for (Identifier id : vs.getIdentifier()) { 151 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) 152 return id.getValue().substring(8); 153 } 154 return null; 155 } 156 157 public static void setOID(ValueSet vs, String oid) { 158 if (!oid.startsWith("urn:oid:")) 159 oid = "urn:oid:" + oid; 160 for (Identifier id : vs.getIdentifier()) { 161 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) { 162 id.setValue(oid); 163 return; 164 } 165 } 166 vs.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid); 167 } 168 169 public static void markStatus(ValueSet vs, String wg, StandardsStatus status, String pckage, String fmm, IWorkerContext context, String normativeVersion) throws FHIRException { 170 if (vs.hasUserData("external.url")) 171 return; 172 173 if (wg != null) { 174 if (!ToolingExtensions.hasExtension(vs, ToolingExtensions.EXT_WORKGROUP) || 175 (!Utilities.existsInList(ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && Utilities.existsInList(wg, "fhir", "vocab"))) { 176 CanonicalResourceUtilities.setHl7WG(vs, wg); 177 } 178 } 179 if (status != null) { 180 StandardsStatus ss = ToolingExtensions.getStandardsStatus(vs); 181 if (ss == null || ss.isLowerThan(status)) 182 ToolingExtensions.setStandardsStatus(vs, status, normativeVersion); 183 if (pckage != null) { 184 if (!vs.hasUserData("ballot.package")) 185 vs.setUserData("ballot.package", pckage); 186 else if (!pckage.equals(vs.getUserString("ballot.package"))) 187 if (!"infrastructure".equals(vs.getUserString("ballot.package"))) 188 System.out.println("Value Set "+vs.getUrl()+": ownership clash "+pckage+" vs "+vs.getUserString("ballot.package")); 189 } 190 if (status == StandardsStatus.NORMATIVE) { 191 vs.setExperimental(false); 192 vs.setStatus(PublicationStatus.ACTIVE); 193 } 194 } 195 if (fmm != null) { 196 String sfmm = ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_FMM_LEVEL); 197 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 198 ToolingExtensions.setIntegerExtension(vs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 199 } 200 if (Integer.parseInt(fmm) <= 1) { 201 vs.setExperimental(true); 202 } 203 } 204 if (vs.hasUserData("cs")) 205 CodeSystemUtilities.markStatus((CodeSystem) vs.getUserData("cs"), wg, status, pckage, fmm, normativeVersion); 206 else if (status == StandardsStatus.NORMATIVE && context != null) { 207 for (ConceptSetComponent csc : vs.getCompose().getInclude()) { 208 if (csc.hasSystem()) { 209 CodeSystem cs = context.fetchCodeSystem(csc.getSystem()); 210 if (cs != null) { 211 CodeSystemUtilities.markStatus(cs, wg, status, pckage, fmm, normativeVersion); 212 } 213 } 214 } 215 } 216 } 217 218 private static int ssval(String status) { 219 if ("Draft".equals("status")) 220 return 1; 221 if ("Informative".equals("status")) 222 return 2; 223 if ("External".equals("status")) 224 return 3; 225 if ("Trial Use".equals("status")) 226 return 3; 227 if ("Normative".equals("status")) 228 return 4; 229 return -1; 230 } 231 232 public static ValueSet generateImplicitValueSet(String uri) { 233 if (uri.startsWith("http://snomed.info/sct")) 234 return generateImplicitSnomedValueSet(uri); 235 if (uri.startsWith("http://loinc.org/vs")) 236 return generateImplicitLoincValueSet(uri); 237 if (uri.equals("http://hl7.org/fhir/ValueSet/mimetypes")) { 238 return generateImplicitMimetypesValueSet(uri); 239 } 240 return null; 241 } 242 243 private static ValueSet generateImplicitMimetypesValueSet(String theUri) { 244 ValueSet valueSet = new ValueSet(); 245 valueSet.setStatus(PublicationStatus.ACTIVE); 246 valueSet.setUrl(theUri); 247 valueSet.setDescription("This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)"); 248 valueSet.getCompose() 249 .addInclude().setSystem("urn:ietf:bcp:13"); 250 return valueSet; 251 } 252 253 private static ValueSet generateImplicitLoincValueSet(String uri) { 254 if ("http://loinc.org/vs".equals(uri)) 255 return makeLoincValueSet(); 256 if (uri.startsWith("http://loinc.org/vs/LL")) 257 return makeAnswerList(makeLoincValueSet(), uri); 258 return null; 259 } 260 261 private static ValueSet makeAnswerList(ValueSet vs, String uri) { 262 vs.setUrl(uri); 263 String c = uri.substring(20); 264 vs.setName("LOINCAnswers"+c); 265 vs.setTitle("LOINC Answer Codes for "+c); 266 vs.getCompose().getIncludeFirstRep().addFilter().setProperty("LIST").setOp(FilterOperator.EQUAL).setValue(c); 267 return vs; 268 } 269 270 private static ValueSet makeLoincValueSet() { 271 ValueSet vs = new ValueSet(); 272 vs.setUrl("http://loinc.org/vs"); 273 vs.setName("LOINCCodes"); 274 vs.setTitle("All LOINC codes"); 275 vs.setCopyright("This content LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at http://loinc.org/terms-of-use"); 276 vs.setStatus(PublicationStatus.ACTIVE); 277 vs.getCompose().addInclude().setSystem("http://loinc.org"); 278 return vs; 279 } 280 281 private static ValueSet generateImplicitSnomedValueSet(String uri) { 282 if ("http://snomed.info/sct?fhir_vs".equals(uri)) 283 return makeImplicitSnomedValueSet(uri); 284 return null; 285 } 286 287 private static ValueSet makeImplicitSnomedValueSet(String uri) { 288 ValueSet vs = new ValueSet(); 289 vs.setUrl(uri); 290 vs.setName("SCTValueSet"); 291 vs.setTitle("SCT ValueSet"); 292 vs.setDescription("All SNOMED CT Concepts"); 293 vs.setCopyright("This value set includes content from SNOMED CT, which is copyright © 2002+ International Health Terminology Standards Development Organisation (SNOMED International), and distributed by agreement between SNOMED International and HL7. Implementer use of SNOMED CT is not covered by this agreement"); 294 vs.setStatus(PublicationStatus.ACTIVE); 295 vs.getCompose().addInclude().setSystem("http://snomed.info/sct"); 296 return vs; 297 } 298 299 public static void setDeprecated(List<ValueSetExpansionPropertyComponent> vsProp, ValueSetExpansionContainsComponent n) { 300 n.addProperty().setCode("status").setValue(new CodeType("deprecated")); 301 for (ValueSetExpansionPropertyComponent o : vsProp) { 302 if ("status".equals(o.getCode())) { 303 return; 304 } 305 } 306 vsProp.add(new ValueSetExpansionPropertyComponent().setCode("status").setUri("http://hl7.org/fhir/concept-properties#status")); 307 } 308 309 310 public static class ConceptReferenceComponentSorter implements Comparator<ConceptReferenceComponent> { 311 312 @Override 313 public int compare(ConceptReferenceComponent o1, ConceptReferenceComponent o2) { 314 return o1.getCode().compareToIgnoreCase(o2.getCode()); 315 } 316 } 317 318 319 public static void sortInclude(ConceptSetComponent inc) { 320 Collections.sort(inc.getConcept(), new ConceptReferenceComponentSorter()); 321 } 322 323 public static String getAllCodesSystem(ValueSet vs) { 324 if (vs.hasCompose()) { 325 ValueSetComposeComponent c = vs.getCompose(); 326 if (c.getExclude().isEmpty() && c.getInclude().size() == 1) { 327 ConceptSetComponent i = c.getIncludeFirstRep(); 328 if (i.hasSystem() && !i.hasValueSet() && !i.hasConcept() && !i.hasFilter()) { 329 return i.getSystem(); 330 } 331 } 332 } 333 return null; 334 } 335 336 public static boolean isDeprecated(ValueSet vs, ValueSetExpansionContainsComponent c) { 337 try { 338 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : c.getProperty()) { 339 if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode())) { 340 return true; 341 } 342 // this, though status should also be set 343 if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 344 return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance())); 345 // legacy 346 if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 347 return ((BooleanType) p.getValue()).getValue(); 348 } 349 StandardsStatus ss = ToolingExtensions.getStandardsStatus(c); 350 if (ss == StandardsStatus.DEPRECATED) { 351 return true; 352 } 353 return false; 354 } catch (FHIRException e) { 355 return false; 356 } 357 } 358 359 public static boolean hasCodeInExpansion(ValueSet vs, Coding code) { 360 return hasCodeInExpansion(vs.getExpansion().getContains(), code); 361 } 362 363 private static boolean hasCodeInExpansion(List<ValueSetExpansionContainsComponent> list, Coding code) { 364 for (ValueSetExpansionContainsComponent c : list) { 365 if (c.getSystem().equals(code.getSystem()) && c.getCode().equals(code.getCode())) { 366 return true; 367 } 368 if (hasCodeInExpansion(c.getContains(), code)) { 369 return true; 370 } 371 } 372 return false; 373 } 374 375 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, String value) { 376 if (value != null) { 377 return addProperty(vs, ctxt, url, code, new StringType(value)); 378 } else { 379 return null; 380 } 381 } 382 383 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, Integer value) { 384 if (value != null) { 385 return addProperty(vs, ctxt, url, code, new IntegerType(value)); 386 } else { 387 return null; 388 } 389 } 390 391 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, DataType value) { 392 code = defineProperty(vs, url, code); 393 org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p = getProperty(ctxt.getProperty(), code); 394 if (p != null) { 395 p.setValue(value); 396 } else { 397 p = ctxt.addProperty().setCode(code).setValue(value); 398 } 399 return p; 400 } 401 402 private static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent getProperty(List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> list, String code) { 403 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent t : list) { 404 if (code.equals(t.getCode())) { 405 return t; 406 } 407 } 408 return null; 409 } 410 411 private static String defineProperty(ValueSet vs, String url, String code) { 412 for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) { 413 if (p.hasUri() && p.getUri().equals(url)) { 414 return p.getCode(); 415 } 416 } 417 for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) { 418 if (p.hasCode() && p.getCode().equals(code)) { 419 p.setUri(url); 420 return code; 421 } 422 } 423 ValueSetExpansionPropertyComponent p = vs.getExpansion().addProperty(); 424 p.setUri(url); 425 p.setCode(code); 426 return code; 427 } 428 429 public static int countExpansion(ValueSet valueset) { 430 int i = valueset.getExpansion().getContains().size(); 431 for (ValueSetExpansionContainsComponent t : valueset.getExpansion().getContains()) { 432 i = i + countExpansion(t); 433 } 434 return i; 435 } 436 437 public static int countExpansion(List<ValueSetExpansionContainsComponent> list) { 438 int i = list.size(); 439 for (ValueSetExpansionContainsComponent t : list) { 440 i = i + countExpansion(t); 441 } 442 return i; 443 } 444 445 private static int countExpansion(ValueSetExpansionContainsComponent c) { 446 int i = c.getContains().size(); 447 for (ValueSetExpansionContainsComponent t : c.getContains()) { 448 i = i + countExpansion(t); 449 } 450 return i; 451 } 452 453 public static Set<String> listSystems(IWorkerContext ctxt, ValueSet vs) { 454 Set<String> systems = new HashSet<>(); 455 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 456 for (CanonicalType ct : inc.getValueSet()) { 457 ValueSet vsr = ctxt.findTxResource(ValueSet.class, ct.asStringValue(), vs); 458 if (vsr != null) { 459 systems.addAll(listSystems(ctxt, vsr)); 460 } 461 } 462 if (inc.hasSystem()) { 463 systems.add(inc.getSystem()); 464 } 465 } 466 return systems; 467 } 468 469 470 public static boolean isIncompleteExpansion(ValueSet valueSet) { 471 if (valueSet.hasExpansion()) { 472 ValueSetExpansionComponent exp = valueSet.getExpansion(); 473 if (exp.hasTotal()) { 474 if (exp.getTotal() != countExpansion(exp.getContains())) { 475 return true; 476 } 477 } 478 } 479 return false; 480 } 481 482 483 public static Set<String> codes(ValueSet vs, CodeSystem cs) { 484 Set<String> res = new HashSet<>(); 485 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 486 if (inc.getSystem().equals(cs.getUrl())) { 487 addCodes(res, inc, cs.getConcept()); 488 } 489 } 490 return res; 491 } 492 493 private static void addCodes(Set<String> res, ConceptSetComponent inc, List<ConceptDefinitionComponent> list) { 494 for (ConceptDefinitionComponent cd : list) { 495 if (cd.hasCode() && (!inc.hasConcept() || inc.hasConcept(cd.getCode()))) { 496 res.add(cd.getCode()); 497 } 498 if (cd.hasConcept()) { 499 addCodes(res, inc, cd.getConcept()); 500 } 501 } 502 } 503}