1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.health.connect.datatypes; 18 19 import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD; 20 21 import static java.util.Objects.hash; 22 import static java.util.Objects.requireNonNull; 23 24 import android.annotation.FlaggedApi; 25 import android.annotation.NonNull; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 29 import java.util.Set; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 33 /** 34 * Represents the FHIR version. This is designed according to <a 35 * href="https://build.fhir.org/versions.html#versions">the official FHIR versions</a> of the Fast 36 * Healthcare Interoperability Resources (FHIR) standard. "label", which represents a 'working' 37 * version, is not supported for now. 38 * 39 * <p>The versions R4 (4.0.1) and R4B (4.3.0) are supported in Health Connect. 40 */ 41 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD) 42 public final class FhirVersion implements Parcelable { 43 private static final FhirVersion R4_FHIR_VERSION = FhirVersion.parseFhirVersion("4.0.1"); 44 private static final FhirVersion R4B_FHIR_VERSION = FhirVersion.parseFhirVersion("4.3.0"); 45 private static final Set<FhirVersion> SUPPORTED_FHIR_VERSIONS = 46 Set.of(R4_FHIR_VERSION, R4B_FHIR_VERSION); 47 48 private final int mMajor; 49 private final int mMinor; 50 private final int mPatch; 51 52 private static final String VERSION_REGEX = "(\\d+)\\.(\\d+)\\.(\\d+)"; 53 FhirVersion(int major, int minor, int patch)54 private FhirVersion(int major, int minor, int patch) { 55 mMajor = major; 56 mMinor = minor; 57 mPatch = patch; 58 validateVersionNumbersNotNegative(); 59 } 60 61 /** 62 * Constructs this object with the data present in {@code parcel}. It should be in the same 63 * order as {@link FhirVersion#writeToParcel}. 64 */ FhirVersion(@onNull Parcel in)65 private FhirVersion(@NonNull Parcel in) { 66 requireNonNull(in); 67 mMajor = in.readInt(); 68 mMinor = in.readInt(); 69 mPatch = in.readInt(); 70 validateVersionNumbersNotNegative(); 71 } 72 73 @NonNull 74 public static final Creator<FhirVersion> CREATOR = 75 new Creator<>() { 76 @Override 77 public FhirVersion createFromParcel(Parcel in) { 78 return new FhirVersion(in); 79 } 80 81 @Override 82 public FhirVersion[] newArray(int size) { 83 return new FhirVersion[size]; 84 } 85 }; 86 87 /** 88 * Creates a {@link FhirVersion} object with the version of string format. 89 * 90 * <p>The format should look like "4.0.1" which contains 3 numbers - major, minor and patch, 91 * separated by ".". This aligns with <a 92 * href="https://build.fhir.org/versions.html#versions">the official FHIR versions</a>. Note 93 * that the "label" is not supported for now, which represents a 'working' version. 94 */ 95 @NonNull parseFhirVersion(@onNull String fhirVersionString)96 public static FhirVersion parseFhirVersion(@NonNull String fhirVersionString) { 97 requireNonNull(fhirVersionString); 98 Pattern pattern = Pattern.compile(VERSION_REGEX); 99 Matcher matcher = pattern.matcher(fhirVersionString); 100 if (!matcher.matches()) { 101 throw new IllegalArgumentException("Invalid FHIR version string: " + fhirVersionString); 102 } 103 return new FhirVersion( 104 Integer.parseInt(matcher.group(1)), 105 Integer.parseInt(matcher.group(2)), 106 Integer.parseInt(matcher.group(3))); 107 } 108 109 /** Returns the major version. */ getMajor()110 public int getMajor() { 111 return mMajor; 112 } 113 114 /** Returns the minor version. */ getMinor()115 public int getMinor() { 116 return mMinor; 117 } 118 119 /** Returns the patch version. */ getPatch()120 public int getPatch() { 121 return mPatch; 122 } 123 124 @Override describeContents()125 public int describeContents() { 126 return 0; 127 } 128 129 /** Populates a {@link Parcel} with the self information. */ 130 @Override writeToParcel(@onNull Parcel dest, int flags)131 public void writeToParcel(@NonNull Parcel dest, int flags) { 132 requireNonNull(dest); 133 dest.writeInt(getMajor()); 134 dest.writeInt(getMinor()); 135 dest.writeInt(getPatch()); 136 } 137 138 /** Indicates whether some other object is "equal to" this one. */ 139 @Override equals(Object o)140 public boolean equals(Object o) { 141 if (this == o) return true; 142 if (!(o instanceof FhirVersion that)) return false; 143 return getMajor() == that.getMajor() 144 && getMinor() == that.getMinor() 145 && getPatch() == that.getPatch(); 146 } 147 148 /** Returns a hash code value for the object. */ 149 @Override hashCode()150 public int hashCode() { 151 return hash(getMajor(), getMinor(), getPatch()); 152 } 153 154 /** Returns the string representation of the FHIR version. */ toString()155 public String toString() { 156 return String.format("%d.%d.%d", mMajor, mMinor, mPatch); 157 } 158 159 /** Returns {@code true} if the {@link FhirVersion} is supported by Health Connect. */ isSupportedFhirVersion()160 public boolean isSupportedFhirVersion() { 161 return SUPPORTED_FHIR_VERSIONS.contains(this); 162 } 163 validateVersionNumbersNotNegative()164 private void validateVersionNumbersNotNegative() { 165 if (mMajor < 0 || mMinor < 0 || mPatch < 0) { 166 throw new IllegalArgumentException("Version numbers can not be negative."); 167 } 168 } 169 } 170