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