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 platform.test.motion.golden
18 
19 import org.json.JSONException
20 import org.json.JSONObject
21 
22 /**
23  * Describes a type safe data point of [T] in a motion test [TimeSeries].
24  *
25  * [DataPoint]s include a [DataPointType] that specified how a value is written to / read from a
26  * JSON golden file. Additionally, some stand-in values such as [NotFoundDataPoint] and
27  * [NullDataPoint] allow to describe why [DataPoint] are absent in [TimeSeries], making the golden
28  * tests more robust.
29  */
30 sealed interface DataPoint<out T> {
31 
asJsonnull32     fun asJson(): Any
33 
34     companion object {
35         fun <T> of(value: T?, type: DataPointType<T>): DataPoint<T> {
36             return if (value != null) {
37                 ValueDataPoint(type.ensureImmutable(value), type)
38             } else {
39                 nullValue()
40             }
41         }
42 
43         fun <T> notFound(): DataPoint<T> {
44             @Suppress("UNCHECKED_CAST") return NotFoundDataPoint.instance as NotFoundDataPoint<T>
45         }
46         fun <T> nullValue(): DataPoint<T> {
47             @Suppress("UNCHECKED_CAST") return NullDataPoint.instance as NullDataPoint<T>
48         }
49 
50         fun <T> unknownType(): DataPoint<T> {
51             @Suppress("UNCHECKED_CAST") return UnknownType.instance as UnknownType<T>
52         }
53     }
54 }
55 
56 /**
57  * Wraps a non-`null` data point value.
58  *
59  * @see DataPoint.of
60  */
61 data class ValueDataPoint<T> internal constructor(val value: T & Any, val type: DataPointType<T>) :
<lambda>null62     DataPoint<T> {
63     override fun asJson() = type.toJson(this.value)
64 
65     override fun toString(): String = "$value (${type.typeName})"
66 }
67 
68 /**
69  * [DataPoint] stand-in to represent `null` data point values.
70  *
71  * @see DataPoint.of
72  * @see DataPoint.nullValue
73  */
74 class NullDataPoint<T> private constructor() : DataPoint<T> {
75 
asJsonnull76     override fun asJson() = JSONObject.NULL
77 
78     companion object {
79         internal val instance = NullDataPoint<Any>()
80 
81         fun isNullValue(jsonValue: Any): Boolean {
82             return jsonValue == JSONObject.NULL
83         }
84     }
85 
toStringnull86     override fun toString(): String = "null"
87 }
88 
89 /**
90  * [DataPoint] stand-in to represent data points that could not be sampled.
91  *
92  * This is usually the case when the subject to sample from did not exist in a specific frame in the
93  * first place.
94  *
95  * @see DataPoint.notFound
96  */
97 class NotFoundDataPoint<T> private constructor() : DataPoint<T> {
98 
99     override fun asJson() = JSONObject().apply { put("type", "not_found") }
100 
101     override fun toString(): String = "{{not_found}}"
102 
103     companion object {
104         internal val instance = NotFoundDataPoint<Any>()
105 
106         fun isNotFoundValue(jsonValue: Any): Boolean {
107             return jsonValue is JSONObject &&
108                 jsonValue.has("type") &&
109                 jsonValue.getString("type") == "not_found"
110         }
111     }
112 }
113 
114 /** [DataPoint] type indicating that a values was not readable during de-serialization. */
115 class UnknownType<T> private constructor() : DataPoint<T> {
116 
asJsonnull117     override fun asJson() = throw JSONException("Feature must not contain UnknownDataPoints")
118 
119     override fun toString(): String = "{{unknown_type}}"
120 
121     companion object {
122         internal val instance = UnknownType<Any>()
123     }
124 }
125