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}