001package org.hl7.fhir.utilities; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import org.apache.commons.lang3.StringUtils; 007import org.hl7.fhir.exceptions.FHIRException; 008 009/* 010 Copyright (c) 2011+, HL7, Inc. 011 All rights reserved. 012 013 Redistribution and use in source and binary forms, with or without modification, 014 are permitted provided that the following conditions are met: 015 016 * Redistributions of source code must retain the above copyright notice, this 017 list of conditions and the following disclaimer. 018 * Redistributions in binary form must reproduce the above copyright notice, 019 this list of conditions and the following disclaimer in the documentation 020 and/or other materials provided with the distribution. 021 * Neither the name of HL7 nor the names of its contributors may be used to 022 endorse or promote products derived from this software without specific 023 prior written permission. 024 025 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 026 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 027 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 028 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 029 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 030 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 031 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 032 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 033 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 034 POSSIBILITY OF SUCH DAMAGE. 035 036 */ 037 038 039public class VersionUtilities { 040 041 042 public static class VersionURLInfo { 043 private String version; 044 private String url; 045 public VersionURLInfo(String version, String url) { 046 super(); 047 this.version = version; 048 this.url = url; 049 } 050 public String getVersion() { 051 return version; 052 } 053 public String getUrl() { 054 return url; 055 } 056 } 057 058 public static final String CURRENT_VERSION = "5.0"; 059 public static final String CURRENT_FULL_VERSION = "5.0.0"; 060 061 public static final String CURRENT_DEFAULT_VERSION = "4.0"; 062 public static final String CURRENT_DEFAULT_FULL_VERSION = "4.0.1"; 063 064 public static String packageForVersion(String v) { 065 if (isR2Ver(v)) { 066 return "hl7.fhir.r2.core"; 067 } 068 if (isR2BVer(v)) { 069 return "hl7.fhir.r2b.core"; 070 } 071 if (isR3Ver(v)) { 072 return "hl7.fhir.r3.core"; 073 } 074 if (isR4Ver(v)) { 075 return "hl7.fhir.r4.core"; 076 } 077 078 if (isR4BVer(v)) { 079 return "hl7.fhir.r4b.core"; 080 } 081 082 if (isR5Ver(v)) { 083 return "hl7.fhir.r5.core"; 084 } 085 086 if ("current".equals(v)) { 087 return "hl7.fhir.r5.core"; 088 } 089 if (v != null && v.startsWith(CURRENT_VERSION)) { 090 return "hl7.fhir.r5.core"; 091 } 092 if (Utilities.existsInList(v, "4.4.0", "4.5.0")) { 093 return "hl7.fhir.r5.core"; 094 } 095 return null; 096 } 097 098 public static String getCurrentVersion(String v) { 099 if (isR2Ver(v)) { 100 return "1.0.2"; 101 } 102 if (isR2BVer(v)) { 103 return "1.4.0"; 104 } 105 if (isR3Ver(v)) { 106 return "3.0.2"; 107 } 108 if (isR4Ver(v)) { 109 return "4.0.1"; 110 } 111 if (isR5Ver(v)) { 112 return "5.0.0"; 113 } 114 if (v != null && v.startsWith(CURRENT_VERSION)) { 115 return "current"; 116 } 117 return v; 118 } 119 120 public static String getCurrentPackageVersion(String v) { 121 if (isR2Ver(v)) { 122 return "1.0"; 123 } 124 if (isR2BVer(v)) { 125 return "1.4"; 126 } 127 if (isR3Ver(v)) { 128 return "3.0"; 129 } 130 if (isR4Ver(v)) { 131 return "4.0"; 132 } 133 if (v != null && v.startsWith(CURRENT_VERSION)) { 134 return "current"; 135 } 136 return v; 137 } 138 139 public static boolean isSupportedVersion(String version) { 140 if (version.contains("-")) { 141 version = version.substring(0, version.indexOf("-")); 142 } 143 return Utilities.existsInList(version, "1.0.2", "1.4.0", "3.0.2", "4.0.1", "4.1.0", "4.3.0", "5.0.0", CURRENT_FULL_VERSION); 144 } 145 146 public static String listSupportedVersions() { 147 return "1.0.2, 1.4.0, 3.0.2, 4.0.1, 4.1.0, 4.3.0, 5.0, " + CURRENT_FULL_VERSION; 148 } 149 150 public static boolean isR5Ver(String ver) { 151 return ver != null && (ver.startsWith("5.0") || ver.startsWith(CURRENT_VERSION) || ver.equals("current")); 152 } 153 154 public static boolean isR4BVer(String ver) { 155 return ver != null && (ver.startsWith("4.1") || ver.startsWith("4.3")); 156 } 157 158 public static boolean isR4Ver(String ver) { 159 return ver != null && ver.startsWith("4.0"); 160 } 161 162 public static boolean isR3Ver(String ver) { 163 return ver != null && ver.startsWith("3.0"); 164 } 165 166 public static boolean isR2BVer(String ver) { 167 return ver != null && ver.startsWith("1.4"); 168 } 169 170 public static boolean isR2Ver(String ver) { 171 return ver != null && ver.startsWith("1.0"); 172 } 173 174 public static boolean versionsCompatible(String v1, String v2) { 175 if (v1 == null || v2 == null) { 176 return false; 177 } 178 String[] v1l = v1.split("\\|"); 179 String[] v2l = v2.split("\\|"); 180 for (String vs1 : v1l) { 181 for (String vs2 : v2l) { 182 String mm1 = getMajMin(vs1); 183 String mm2 = getMajMin(vs2); 184 if (mm1 == null || mm2 == null) { 185 return false; 186 } else { 187 if (mm1.equals(mm2)) { 188 return true; 189 } 190 } 191 } 192 } 193 return false; 194 } 195 196 public static boolean isCorePackage(String s) { 197 if (s.contains("#")) { 198 s = s.substring(0, s.indexOf("#")); 199 } 200 return Utilities.existsInList(s, "hl7.fhir.core","hl7.fhir.r2.core", "hl7.fhir.r2b.core", "hl7.fhir.r3.core", "hl7.fhir.r4.core"); 201 } 202 203 public static String getMajMin(String version) { 204 if (version == null) 205 return null; 206 207 if ("current".equals(version)) { 208 return CURRENT_VERSION; 209 } 210 211 if (Utilities.charCount(version, '.') == 1) { 212 String[] p = version.split("\\."); 213 return p[0]+"."+p[1]; 214 } else if (Utilities.charCount(version, '.') == 2) { 215 String[] p = version.split("\\."); 216 return p[0]+"."+p[1]; 217 } else { 218 return null; 219 } 220 } 221 222 public static String getPatch(String version) { 223 if (version == null) 224 return null; 225 if (Utilities.charCount(version, '.') == 2) { 226 String[] p = version.split("\\."); 227 return p[2]; 228 } 229 return null; 230 } 231 232 public static boolean isSemVer(String version) { 233 if (Utilities.charCount(version, '.') != 2) { 234 return false; 235 } 236 String[] p = version.split("\\."); 237 if (p[2].contains("-")) { 238 p[2] = p[2].substring(0, p[2].indexOf("-")); 239 } 240 return Utilities.isInteger(p[0]) && Utilities.isInteger(p[1]) && Utilities.isInteger(p[2]); 241 } 242 243 /** 244 * return true if the current version equals test, or later, 245 * so if a feature is defined in 4.0, if (VersionUtilities.isThisOrLater("4.0", version)) 246 * <p> 247 * This method tries to perform a numeric parse, so that <code>0.9</code> will be considered below <code>0.10</code> 248 * in accordance with SemVer. If either side contains a non-numeric character in a version string, a simple text 249 * compare will be done instead. 250 * </p> 251 * 252 * @param test The value to compare to 253 * @param current The value being compared 254 * @return Is {@literal current} later or equal to {@literal test}? For example, if <code>this = 0.5</code> and <code>current = 0.6</code> this method will return true 255 */ 256 public static boolean isThisOrLater(String test, String current) { 257 if (test == null || current == null) { 258 return false; 259 } 260 String t = getMajMin(test); 261 String c = getMajMin(current); 262 if (t == null || c == null) { 263 return false; 264 } 265 if (c.compareTo(t) == 0) { 266 return isMajMinOrLaterPatch(test, current); 267 } 268 269 String[] testParts = t.split("\\."); 270 String[] currentParts = c.split("\\."); 271 272 for (int i = 0; i < Math.max(testParts.length, currentParts.length); i++) { 273 if (i == testParts.length) { 274 return true; 275 } else if (i == currentParts.length) { 276 return false; 277 } 278 String testPart = testParts[i]; 279 String currentPart = currentParts[i]; 280 if (testPart.equals(currentPart)) { 281 continue; 282 } 283 return compareVersionPart(testPart, currentPart); 284 } 285 286 return true; 287 } 288 289 private static boolean compareVersionPart(String theTestPart, String theCurrentPart) { 290 if (StringUtils.isNumeric(theTestPart) && StringUtils.isNumeric(theCurrentPart)) { 291 return Integer.parseInt(theCurrentPart) - Integer.parseInt(theTestPart) >= 0; 292 } else { 293 return theCurrentPart.compareTo(theTestPart) >= 0; 294 } 295 } 296 297 /** 298 * return true if the current version equals test for major and min, or later patch 299 * 300 * @param test 301 * @param current 302 * @return 303 */ 304 public static boolean isMajMinOrLaterPatch(String test, String current) { 305 String t = getMajMin(test); 306 String c = getMajMin(current); 307 if (c != null && t != null && c.compareTo(t) == 0) { 308 String pt = getPatch(test); 309 String pc = getPatch(current); 310 if (pt==null || "x".equals(pt)) { 311 return true; 312 } 313 if (pc!=null) { 314 if (pt.contains("-") && !pc.contains("-")) { 315 pt = pt.substring(0, pt.indexOf("-")); 316 return pt.compareTo(pc) >= 0; 317 } else { 318 return compareVersionPart(pt, pc); 319 } 320 } 321 } 322 return false; 323 } 324 325 public static String incMajorVersion(String v) { 326 assert isSemVer(v); 327 int[] parts = splitParts(v); 328 return Integer.toString(parts[0]+1)+".0.0"; 329 } 330 331 public static String incMinorVersion(String v) { 332 assert isSemVer(v); 333 int[] parts = splitParts(v); 334 return Integer.toString(parts[0])+"."+Integer.toString(parts[1]+1)+".0"; 335 } 336 337 public static String incPatchVersion(String v) { 338 assert isSemVer(v); 339 int[] parts = splitParts(v); 340 return Integer.toString(parts[0])+"."+Integer.toString(parts[1])+"."+Integer.toString(parts[2]+1); 341 } 342 343 private static int[] splitParts(String v) { 344 String[] p = v.split("\\."); 345 int[] i = new int[] {Integer.parseInt(p[0]),Integer.parseInt(p[1]),Integer.parseInt(p[2])}; 346 return i; 347 } 348 349 public static String versionFromCode(String version) { 350 if ("r2".equals(version)) { 351 return "1.0.2"; 352 } 353 if ("r2b".equals(version)) { 354 return "1.4.0"; 355 } 356 if ("r3".equals(version)) { 357 return "3.0.2"; 358 } 359 if ("r4".equals(version)) { 360 return "4.0.1"; 361 } 362 if ("r4".equals(version)) { 363 return "4.0.1"; 364 } 365 if ("r4b".equals(version)) { 366 return "4.3.0"; 367 } 368 if ("r5".equals(version)) { 369 return "5.0.0"; 370 } 371 throw new FHIRException("Unknown version "+version); 372 } 373 374 public static VersionURLInfo parseVersionUrl(String url) { 375 if (url.length() < 24) { 376 return null; 377 } 378 String v = url.substring(20, 24); 379 if (v.endsWith("/")) { 380 v = v.substring(0, v.length()-1); 381 if (Utilities.existsInList(v, "1.0", "1.4", "3.0", "4.0", "5.0", CURRENT_VERSION)) { 382 return new VersionURLInfo(v, "http://hl7.org/fhir/"+url.substring(24)); 383 } 384 } 385 return null; 386 } 387 388 public static List<String> getCanonicalResourceNames(String version) { 389 ArrayList<String> res = new ArrayList<String>(); 390 if (isR2Ver(version) || isR2BVer(version)) { 391 res.add("ValueSet"); 392 res.add("ConceptMap"); 393 res.add("NamingSystem"); 394 res.add("StructureDefinition"); 395 res.add("DataElement"); 396 res.add("Conformance"); 397 res.add("OperationDefinition"); 398 res.add("SearchParameter"); 399 res.add("ImplementationGuide"); 400 res.add("TestScript"); 401 } 402 if (isR3Ver(version)) { 403 res.add("CodeSystem"); 404 res.add("CapabilityStatement"); 405 res.add("StructureDefinition"); 406 res.add("ImplementationGuide"); 407 res.add("SearchParameter"); 408 res.add("MessageDefinition"); 409 res.add("OperationDefinition"); 410 res.add("CompartmentDefinition"); 411 res.add("StructureMap"); 412 res.add("GraphDefinition"); 413 res.add("DataElement"); 414 res.add("CodeSystem"); 415 res.add("ValueSet"); 416 res.add("ConceptMap"); 417 res.add("ExpansionProfile"); 418 res.add("Questionnaire"); 419 res.add("ActivityDefinition"); 420 res.add("ServiceDefinition"); 421 res.add("PlanDefinition"); 422 res.add("Measure"); 423 res.add("TestScript"); 424 425 } 426 if (isR4Ver(version)) { 427 res.add("CodeSystem"); 428 res.add("ActivityDefinition"); 429 res.add("CapabilityStatement"); 430 res.add("ChargeItemDefinition"); 431 res.add("CodeSystem"); 432 res.add("CompartmentDefinition"); 433 res.add("ConceptMap"); 434 res.add("EffectEvidenceSynthesis"); 435 res.add("EventDefinition"); 436 res.add("Evidence"); 437 res.add("EvidenceVariable"); 438 res.add("ExampleScenario"); 439 res.add("GraphDefinition"); 440 res.add("ImplementationGuide"); 441 res.add("Library"); 442 res.add("Measure"); 443 res.add("MessageDefinition"); 444 res.add("NamingSystem"); 445 res.add("OperationDefinition"); 446 res.add("PlanDefinition"); 447 res.add("Questionnaire"); 448 res.add("ResearchDefinition"); 449 res.add("ResearchElementDefinition"); 450 res.add("RiskEvidenceSynthesis"); 451 res.add("SearchParameter"); 452 res.add("StructureDefinition"); 453 res.add("StructureMap"); 454 res.add("TerminologyCapabilities"); 455 res.add("TestScript"); 456 res.add("ValueSet"); 457 } 458 if (isR4BVer(version)) { 459 res.add("ActivityDefinition"); 460 res.add("CapabilityStatement"); 461 res.add("ChargeItemDefinition"); 462 res.add("Citation"); 463 res.add("CodeSystem"); 464 res.add("CompartmentDefinition"); 465 res.add("ConceptMap"); 466 res.add("EventDefinition"); 467 res.add("Evidence"); 468 res.add("EvidenceReport"); 469 res.add("EvidenceVariable"); 470 res.add("ExampleScenario"); 471 res.add("GraphDefinition"); 472 res.add("ImplementationGuide"); 473 res.add("Library"); 474 res.add("Measure"); 475 res.add("MessageDefinition"); 476 res.add("NamingSystem"); 477 res.add("OperationDefinition"); 478 res.add("PlanDefinition"); 479 res.add("Questionnaire"); 480 res.add("ResearchDefinition"); 481 res.add("ResearchElementDefinition"); 482 res.add("SearchParameter"); 483 res.add("StructureDefinition"); 484 res.add("StructureMap"); 485 res.add("SubscriptionTopic"); 486 res.add("TerminologyCapabilities"); 487 res.add("TestScript"); 488 res.add("ValueSet"); 489 } 490 491 if (isR5Ver(version) || "current".equals(version)) { 492 493 res.add("ActivityDefinition"); 494 res.add("CapabilityStatement"); 495 res.add("ChargeItemDefinition"); 496 res.add("Citation"); 497 res.add("CodeSystem"); 498 res.add("CompartmentDefinition"); 499 res.add("ConceptMap"); 500 res.add("ConditionDefinition"); 501 res.add("EventDefinition"); 502 res.add("Evidence"); 503 res.add("EvidenceReport"); 504 res.add("EvidenceVariable"); 505 res.add("ExampleScenario"); 506 res.add("GraphDefinition"); 507 res.add("ImplementationGuide"); 508 res.add("Library"); 509 res.add("Measure"); 510 res.add("MessageDefinition"); 511 res.add("NamingSystem"); 512 res.add("OperationDefinition"); 513 res.add("PlanDefinition"); 514 res.add("Questionnaire"); 515 res.add("SearchParameter"); 516 res.add("StructureDefinition"); 517 res.add("StructureMap"); 518 res.add("TerminologyCapabilities"); 519 res.add("TestScript"); 520 res.add("ValueSet"); 521 } 522 return res; 523 } 524 525 public static String getVersionForPackage(String pid) { 526 if (pid.startsWith("hl7.fhir.r")) { 527 String[] p = pid.split("\\."); 528 return versionFromCode(p[2]); 529 } 530 return null; 531 } 532 533 public static boolean versionsMatch(String v1, String v2) { 534 String mm1 = getMajMin(v1); 535 String mm2 = getMajMin(v2); 536 return mm1 != null && mm2 != null && mm1.equals(mm2); 537 } 538 539 public static boolean isR5VerOrLater(String version) { 540 if (version == null) { 541 return false; 542 } 543 if (version.startsWith(CURRENT_VERSION) || version.equals("current")) { 544 return true; 545 } 546 String v = getMajMin(version); 547 return v.compareTo("4.5") >= 0; 548 } 549 550 public static String removeVersionFromCanonical(String url) { 551 if (url == null) { 552 return null; 553 } 554 if (url.contains("|")) { 555 return url.substring(0, url.indexOf("|")); 556 } else { 557 return url; 558 } 559 } 560 561 public static boolean isR4Plus(String version) { 562 return version != null && (version.startsWith("4.") || version.startsWith("5.") || "current".equals(version)); 563 } 564 565 public static boolean refersTo(String refVer, String v) { 566 if (v.length() > refVer.length()) { 567 v = v.substring(0, refVer.length()); 568 } 569 return refVer.equals(v); 570 } 571 572 public static String getSpecUrl(String v) { 573 if (v.contains("-cibuild")) { 574 return "http://build.fhir.org"; 575 } 576 if (v.contains("-")) { 577 return "http://hl7.org/fhir/"+v; 578 } 579 580 switch (getMajMin(v)) { 581 case "1.0" : return "http://hl7.org/fhir/DSTU1"; 582 case "1.4" : return "http://hl7.org/fhir/DSTU2"; 583 case "3.0" : return "http://hl7.org/fhir/STU3"; 584 case "4.0" : return "http://hl7.org/fhir/R4"; 585 case "4.3" : return "http://hl7.org/fhir/R4B"; 586 case "5.0" : return "http://hl7.org/fhir/5.0.0-snapshot3"; 587 default: 588 return "http://hl7.org/fhir"; 589 } 590 } 591 592 public static String getNameForVersion(String v) { 593 switch (getMajMin(v)) { 594 case "1.0" : return "R2"; 595 case "1.4" : return "R2B"; 596 case "3.0" : return "R3"; 597 case "4.0" : return "R4"; 598 case "4.3" : return "R4B"; 599 case "5.0" : return "R5"; 600 default: 601 return "R?"; 602 } 603 } 604 605 606}