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 = "4.6"; 059 public static final String CURRENT_FULL_VERSION = "4.6.0"; 060 061 public static String packageForVersion(String v) { 062 if (isR2Ver(v)) { 063 return "hl7.fhir.r2.core"; 064 } 065 if (isR2BVer(v)) { 066 return "hl7.fhir.r2b.core"; 067 } 068 if (isR3Ver(v)) { 069 return "hl7.fhir.r3.core"; 070 } 071 if (isR4Ver(v)) { 072 return "hl7.fhir.r4.core"; 073 } 074 075 if (isR4BVer(v)) { 076 return "hl7.fhir.r4b.core"; 077 } 078 079 if ("current".equals(v)) { 080 return "hl7.fhir.r5.core"; 081 } 082 if (v != null && v.startsWith(CURRENT_VERSION)) { 083 return "hl7.fhir.r5.core"; 084 } 085 if (Utilities.existsInList(v, "4.4.0", "4.5.0")) { 086 return "hl7.fhir.r5.core"; 087 } 088 return null; 089 } 090 091 public static String getCurrentVersion(String v) { 092 if (isR2Ver(v)) { 093 return "1.0.2"; 094 } 095 if (isR2BVer(v)) { 096 return "1.4.0"; 097 } 098 if (isR3Ver(v)) { 099 return "3.0.2"; 100 } 101 if (isR4Ver(v)) { 102 return "4.0.1"; 103 } 104 if (v != null && v.startsWith(CURRENT_VERSION)) { 105 return "current"; 106 } 107 return v; 108 } 109 110 public static String getCurrentPackageVersion(String v) { 111 if (isR2Ver(v)) { 112 return "1.0"; 113 } 114 if (isR2BVer(v)) { 115 return "1.4"; 116 } 117 if (isR3Ver(v)) { 118 return "3.0"; 119 } 120 if (isR4Ver(v)) { 121 return "4.0"; 122 } 123 if (v != null && v.startsWith(CURRENT_VERSION)) { 124 return "current"; 125 } 126 return v; 127 } 128 129 public static boolean isSupportedVersion(String version) { 130 if (version.contains("-")) { 131 version = version.substring(0, version.indexOf("-")); 132 } 133 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); 134 } 135 136 public static String listSupportedVersions() { 137 return "1.0.2, 1.4.0, 3.0.2, 4.0.1, 4.1.0, 4.3.0, 5.0, " + CURRENT_FULL_VERSION; 138 } 139 140 public static boolean isR5Ver(String ver) { 141 return ver != null && (ver.startsWith("5.0") || ver.startsWith(CURRENT_VERSION) || ver.equals("current")); 142 } 143 144 public static boolean isR4BVer(String ver) { 145 return ver != null && (ver.startsWith("4.1") || ver.startsWith("4.3")); 146 } 147 148 public static boolean isR4Ver(String ver) { 149 return ver != null && ver.startsWith("4.0"); 150 } 151 152 public static boolean isR3Ver(String ver) { 153 return ver != null && ver.startsWith("3.0"); 154 } 155 156 public static boolean isR2BVer(String ver) { 157 return ver != null && ver.startsWith("1.4"); 158 } 159 160 public static boolean isR2Ver(String ver) { 161 return ver != null && ver.startsWith("1.0"); 162 } 163 164 public static boolean versionsCompatible(String v1, String v2) { 165 if (v1 == null || v2 == null) { 166 return false; 167 } 168 String[] v1l = v1.split("\\|"); 169 String[] v2l = v2.split("\\|"); 170 for (String vs1 : v1l) { 171 for (String vs2 : v2l) { 172 String mm1 = getMajMin(vs1); 173 String mm2 = getMajMin(vs2); 174 if (mm1 == null || mm2 == null) { 175 return false; 176 } else { 177 if (mm1.equals(mm2)) { 178 return true; 179 } 180 } 181 } 182 } 183 return false; 184 } 185 186 public static boolean isCorePackage(String s) { 187 if (s.contains("#")) { 188 s = s.substring(0, s.indexOf("#")); 189 } 190 return Utilities.existsInList(s, "hl7.fhir.core","hl7.fhir.r2.core", "hl7.fhir.r2b.core", "hl7.fhir.r3.core", "hl7.fhir.r4.core"); 191 } 192 193 public static String getMajMin(String version) { 194 if (version == null) 195 return null; 196 197 if ("current".equals(version)) { 198 return CURRENT_VERSION; 199 } 200 201 if (Utilities.charCount(version, '.') == 1) { 202 String[] p = version.split("\\."); 203 return p[0]+"."+p[1]; 204 } else if (Utilities.charCount(version, '.') == 2) { 205 String[] p = version.split("\\."); 206 return p[0]+"."+p[1]; 207 } else { 208 return null; 209 } 210 } 211 212 public static String getPatch(String version) { 213 if (version == null) 214 return null; 215 if (Utilities.charCount(version, '.') == 2) { 216 String[] p = version.split("\\."); 217 return p[2]; 218 } 219 return null; 220 } 221 222 public static boolean isSemVer(String version) { 223 if (Utilities.charCount(version, '.') != 2) { 224 return false; 225 } 226 String[] p = version.split("\\."); 227 return Utilities.isInteger(p[0]) && Utilities.isInteger(p[1]) && Utilities.isInteger(p[2]); 228 } 229 230 /** 231 * return true if the current version equals test, or later, 232 * so if a feature is defined in 4.0, if (VersionUtilities.isThisOrLater("4.0", version)) 233 * <p> 234 * This method tries to perform a numeric parse, so that <code>0.9</code> will be considered below <code>0.10</code> 235 * in accordance with SemVer. If either side contains a non-numeric character in a version string, a simple text 236 * compare will be done instead. 237 * </p> 238 * 239 * @param test The value to compare to 240 * @param current The value being compared 241 * @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 242 */ 243 public static boolean isThisOrLater(String test, String current) { 244 if (test == null || current == null) { 245 return false; 246 } 247 String t = getMajMin(test); 248 String c = getMajMin(current); 249 if (t == null || c == null) { 250 return false; 251 } 252 if (c.compareTo(t) == 0) { 253 return isMajMinOrLaterPatch(test, current); 254 } 255 256 String[] testParts = t.split("\\."); 257 String[] currentParts = c.split("\\."); 258 259 for (int i = 0; i < Math.max(testParts.length, currentParts.length); i++) { 260 if (i == testParts.length) { 261 return true; 262 } else if (i == currentParts.length) { 263 return false; 264 } 265 String testPart = testParts[i]; 266 String currentPart = currentParts[i]; 267 if (testPart.equals(currentPart)) { 268 continue; 269 } 270 return compareVersionPart(testPart, currentPart); 271 } 272 273 return true; 274 } 275 276 private static boolean compareVersionPart(String theTestPart, String theCurrentPart) { 277 if (StringUtils.isNumeric(theTestPart) && StringUtils.isNumeric(theCurrentPart)) { 278 return Integer.parseInt(theCurrentPart) - Integer.parseInt(theTestPart) >= 0; 279 } else { 280 return theCurrentPart.compareTo(theTestPart) >= 0; 281 } 282 } 283 284 /** 285 * return true if the current version equals test for major and min, or later patch 286 * 287 * @param test 288 * @param current 289 * @return 290 */ 291 public static boolean isMajMinOrLaterPatch(String test, String current) { 292 String t = getMajMin(test); 293 String c = getMajMin(current); 294 if (c != null && c.compareTo(t) == 0) { 295 String pt = getPatch(test); 296 String pc = getPatch(current); 297 if (pt==null || "x".equals(pt)) { 298 return true; 299 } 300 if (pc!=null) { 301 return compareVersionPart(pt, pc); 302 } 303 } 304 return false; 305 } 306 307 public static String incMajorVersion(String v) { 308 assert isSemVer(v); 309 int[] parts = splitParts(v); 310 return Integer.toString(parts[0]+1)+".0.0"; 311 } 312 313 public static String incMinorVersion(String v) { 314 assert isSemVer(v); 315 int[] parts = splitParts(v); 316 return Integer.toString(parts[0])+"."+Integer.toString(parts[1]+1)+".0"; 317 } 318 319 public static String incPatchVersion(String v) { 320 assert isSemVer(v); 321 int[] parts = splitParts(v); 322 return Integer.toString(parts[0])+"."+Integer.toString(parts[1])+"."+Integer.toString(parts[2]+1); 323 } 324 325 private static int[] splitParts(String v) { 326 String[] p = v.split("\\."); 327 int[] i = new int[] {Integer.parseInt(p[0]),Integer.parseInt(p[1]),Integer.parseInt(p[2])}; 328 return i; 329 } 330 331 public static String versionFromCode(String version) { 332 if ("r2".equals(version)) { 333 return "1.0.2"; 334 } 335 if ("r2b".equals(version)) { 336 return "1.4.0"; 337 } 338 if ("r3".equals(version)) { 339 return "3.0.2"; 340 } 341 if ("r4".equals(version)) { 342 return "4.0.1"; 343 } 344 if ("r5".equals(version)) { 345 return CURRENT_FULL_VERSION; 346 } 347 throw new FHIRException("Unknown version "+version); 348 } 349 350 public static VersionURLInfo parseVersionUrl(String url) { 351 if (url.length() < 24) { 352 return null; 353 } 354 String v = url.substring(20, 24); 355 if (v.endsWith("/")) { 356 v = v.substring(0, v.length()-1); 357 if (Utilities.existsInList(v, "1.0", "1.4", "3.0", "4.0", "5.0", CURRENT_VERSION)) { 358 return new VersionURLInfo(v, "http://hl7.org/fhir/"+url.substring(24)); 359 } 360 } 361 return null; 362 } 363 364 public static List<String> getCanonicalResourceNames(String version) { 365 ArrayList<String> res = new ArrayList<String>(); 366 if (isR2Ver(version) || isR2BVer(version)) { 367 res.add("ValueSet"); 368 res.add("ConceptMap"); 369 res.add("NamingSystem"); 370 res.add("StructureDefinition"); 371 res.add("DataElement"); 372 res.add("Conformance"); 373 res.add("OperationDefinition"); 374 res.add("SearchParameter"); 375 res.add("ImplementationGuide"); 376 res.add("TestScript"); 377 } 378 if (isR3Ver(version)) { 379 res.add("CodeSystem"); 380 res.add("CapabilityStatement"); 381 res.add("StructureDefinition"); 382 res.add("ImplementationGuide"); 383 res.add("SearchParameter"); 384 res.add("MessageDefinition"); 385 res.add("OperationDefinition"); 386 res.add("CompartmentDefinition"); 387 res.add("StructureMap"); 388 res.add("GraphDefinition"); 389 res.add("DataElement"); 390 res.add("CodeSystem"); 391 res.add("ValueSet"); 392 res.add("ConceptMap"); 393 res.add("ExpansionProfile"); 394 res.add("Questionnaire"); 395 res.add("ActivityDefinition"); 396 res.add("ServiceDefinition"); 397 res.add("PlanDefinition"); 398 res.add("Measure"); 399 res.add("TestScript"); 400 401 } 402 if (isR4Ver(version)) { 403 res.add("CodeSystem"); 404 res.add("ActivityDefinition"); 405 res.add("CapabilityStatement"); 406 res.add("ChargeItemDefinition"); 407 res.add("CodeSystem"); 408 res.add("CompartmentDefinition"); 409 res.add("ConceptMap"); 410 res.add("EffectEvidenceSynthesis"); 411 res.add("EventDefinition"); 412 res.add("Evidence"); 413 res.add("EvidenceVariable"); 414 res.add("ExampleScenario"); 415 res.add("GraphDefinition"); 416 res.add("ImplementationGuide"); 417 res.add("Library"); 418 res.add("Measure"); 419 res.add("MessageDefinition"); 420 res.add("NamingSystem"); 421 res.add("OperationDefinition"); 422 res.add("PlanDefinition"); 423 res.add("Questionnaire"); 424 res.add("ResearchDefinition"); 425 res.add("ResearchElementDefinition"); 426 res.add("RiskEvidenceSynthesis"); 427 res.add("SearchParameter"); 428 res.add("StructureDefinition"); 429 res.add("StructureMap"); 430 res.add("TerminologyCapabilities"); 431 res.add("TestScript"); 432 res.add("ValueSet"); 433 } 434 if (isR4BVer(version)) { 435 res.add("ActivityDefinition"); 436 res.add("CapabilityStatement"); 437 res.add("ChargeItemDefinition"); 438 res.add("Citation"); 439 res.add("CodeSystem"); 440 res.add("CompartmentDefinition"); 441 res.add("ConceptMap"); 442 res.add("EventDefinition"); 443 res.add("Evidence"); 444 res.add("EvidenceReport"); 445 res.add("EvidenceVariable"); 446 res.add("ExampleScenario"); 447 res.add("GraphDefinition"); 448 res.add("ImplementationGuide"); 449 res.add("Library"); 450 res.add("Measure"); 451 res.add("MessageDefinition"); 452 res.add("NamingSystem"); 453 res.add("OperationDefinition"); 454 res.add("PlanDefinition"); 455 res.add("Questionnaire"); 456 res.add("ResearchDefinition"); 457 res.add("ResearchElementDefinition"); 458 res.add("SearchParameter"); 459 res.add("StructureDefinition"); 460 res.add("StructureMap"); 461 res.add("SubscriptionTopic"); 462 res.add("TerminologyCapabilities"); 463 res.add("TestScript"); 464 res.add("ValueSet"); 465 } 466 467 if (isR5Ver(version) || "current".equals(version)) { 468 469 res.add("ActivityDefinition"); 470 res.add("CapabilityStatement"); 471 res.add("CapabilityStatement2"); 472 res.add("ChargeItemDefinition"); 473 res.add("Citation"); 474 res.add("CodeSystem"); 475 res.add("CompartmentDefinition"); 476 res.add("ConceptMap"); 477 res.add("ConditionDefinition"); 478 res.add("EventDefinition"); 479 res.add("Evidence"); 480 res.add("EvidenceReport"); 481 res.add("EvidenceVariable"); 482 res.add("ExampleScenario"); 483 res.add("GraphDefinition"); 484 res.add("ImplementationGuide"); 485 res.add("Library"); 486 res.add("Measure"); 487 res.add("MessageDefinition"); 488 res.add("NamingSystem"); 489 res.add("OperationDefinition"); 490 res.add("PlanDefinition"); 491 res.add("Questionnaire"); 492 res.add("SearchParameter"); 493 res.add("StructureDefinition"); 494 res.add("StructureMap"); 495 res.add("TerminologyCapabilities"); 496 res.add("TestScript"); 497 res.add("ValueSet"); 498 } 499 return res; 500 } 501 502 public static String getVersionForPackage(String pid) { 503 if (pid.startsWith("hl7.fhir.r")) { 504 String[] p = pid.split("\\."); 505 return versionFromCode(p[2]); 506 } 507 return null; 508 } 509 510 public static boolean versionsMatch(String v1, String v2) { 511 String mm1 = getMajMin(v1); 512 String mm2 = getMajMin(v2); 513 return mm1 != null && mm2 != null && mm1.equals(mm2); 514 } 515 516 public static boolean isR5VerOrLater(String version) { 517 if (version == null) { 518 return false; 519 } 520 if (version.startsWith(CURRENT_VERSION) || version.equals("current")) { 521 return true; 522 } 523 String v = getMajMin(version); 524 return v.compareTo("4.5") >= 0; 525 } 526 527 528}