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}