1 /* 2 * Copyright (C) 2023 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.validation; 18 19 import android.health.connect.datatypes.TimeInterval; 20 21 import java.time.Instant; 22 import java.util.Comparator; 23 import java.util.List; 24 import java.util.Set; 25 26 /** 27 * An util class for HC datatype validation 28 * 29 * @hide 30 */ 31 public final class ValidationUtils { 32 public static final String INTDEF_VALIDATION_ERROR_PREFIX = "Unknown Intdef value"; 33 34 /** Requires integer value to be within the range. */ requireInRange(int value, int lowerBound, int upperBound, String name)35 public static void requireInRange(int value, int lowerBound, int upperBound, String name) { 36 if (value < lowerBound) { 37 throw new IllegalArgumentException( 38 name + " must not be less than " + lowerBound + ", currently " + value); 39 } 40 41 if (value > upperBound) { 42 throw new IllegalArgumentException( 43 name + " must not be more than " + upperBound + ", currently " + value); 44 } 45 } 46 47 /** Requires long value to be within the range. */ requireInRange(long value, long lowerBound, long upperBound, String name)48 public static void requireInRange(long value, long lowerBound, long upperBound, String name) { 49 if (value < lowerBound) { 50 throw new IllegalArgumentException( 51 name + " must not be less than " + lowerBound + ", currently " + value); 52 } 53 54 if (value > upperBound) { 55 throw new IllegalArgumentException( 56 name + " must not be more than " + upperBound + ", currently " + value); 57 } 58 } 59 60 /** Requires double value to be non negative. */ requireNonNegative(double value, String name)61 public static void requireNonNegative(double value, String name) { 62 if (value < 0.0) { 63 throw new IllegalArgumentException(name + "must be non-negative, currently " + value); 64 } 65 } 66 67 /** Requires double value to be within the range. */ requireInRange( double value, double lowerBound, double upperBound, String name)68 public static void requireInRange( 69 double value, double lowerBound, double upperBound, String name) { 70 if (value < lowerBound) { 71 throw new IllegalArgumentException( 72 name + " must not be less than " + lowerBound + ", currently " + value); 73 } 74 75 if (value > upperBound) { 76 throw new IllegalArgumentException( 77 name + " must not be more than " + upperBound + ", currently " + value); 78 } 79 } 80 81 /** Requires an integer value to be among the set of allowed values. */ validateIntDefValue(int value, Set<Integer> allowedValues, String name)82 public static void validateIntDefValue(int value, Set<Integer> allowedValues, String name) { 83 if (!allowedValues.contains(value)) { 84 throw new IllegalArgumentException( 85 INTDEF_VALIDATION_ERROR_PREFIX + ": " + value + " for Intdef: " + name + "."); 86 } 87 } 88 89 /** Requires list of times to be within the range. */ validateSampleStartAndEndTime( Instant sessionStartTime, Instant sessionEndTime, List<Instant> timeInstants)90 public static void validateSampleStartAndEndTime( 91 Instant sessionStartTime, Instant sessionEndTime, List<Instant> timeInstants) { 92 if (timeInstants.size() > 0) { 93 Instant minTime = timeInstants.get(0); 94 Instant maxTime = timeInstants.get(0); 95 for (Instant instant : timeInstants) { 96 if (instant.isBefore(minTime)) { 97 minTime = instant; 98 } 99 if (instant.isAfter(maxTime)) { 100 maxTime = instant; 101 } 102 } 103 if (minTime.isBefore(sessionStartTime) || maxTime.isAfter(sessionEndTime)) { 104 throw new IllegalArgumentException( 105 "Time instant values must be within session interval"); 106 } 107 } 108 } 109 110 /** Requires comparable class to be within the range. */ requireInRangeIfExists( Comparable<T> value, T threshold, T limit, String name)111 public static <T extends Comparable<T>> void requireInRangeIfExists( 112 Comparable<T> value, T threshold, T limit, String name) { 113 if (value != null && value.compareTo(threshold) < 0) { 114 throw new IllegalArgumentException( 115 name + " must not be less than " + threshold + ", currently " + value); 116 } 117 118 if (value != null && value.compareTo(limit) > 0) { 119 throw new IllegalArgumentException( 120 name + " must not be more than " + limit + ", currently " + value); 121 } 122 } 123 124 /** 125 * Sorts time interval holders by time intervals. Validates that time intervals do not overlap 126 * and within parent start and end times. 127 */ 128 public static List<? extends TimeInterval.TimeIntervalHolder> sortAndValidateTimeIntervalHolders( Instant parentStartTime, Instant parentEndTime, List<? extends TimeInterval.TimeIntervalHolder> intervalHolders)129 sortAndValidateTimeIntervalHolders( 130 Instant parentStartTime, 131 Instant parentEndTime, 132 List<? extends TimeInterval.TimeIntervalHolder> intervalHolders) { 133 if (intervalHolders.isEmpty()) { 134 return intervalHolders; 135 } 136 137 String intervalsName = intervalHolders.get(0).getClass().getSimpleName(); 138 intervalHolders.sort(Comparator.comparing(TimeInterval.TimeIntervalHolder::getInterval)); 139 TimeInterval currentInterval, previousInterval; 140 for (int i = 0; i < intervalHolders.size(); i++) { 141 currentInterval = intervalHolders.get(i).getInterval(); 142 if (currentInterval.getStartTime().isBefore(parentStartTime) 143 || currentInterval.getEndTime().isAfter(parentEndTime)) { 144 throw new IllegalArgumentException( 145 intervalsName 146 + ": time intervals must be within parent session time interval."); 147 } 148 149 if (i != 0) { 150 previousInterval = intervalHolders.get(i - 1).getInterval(); 151 if (previousInterval.getEndTime().isAfter(currentInterval.getStartTime())) { 152 throw new IllegalArgumentException( 153 intervalsName + ": time intervals must not overlap."); 154 } 155 } 156 } 157 return intervalHolders; 158 } 159 } 160