1 /*
2  * Copyright (C) 2022 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 com.android.systemui.log.table
18 
19 import androidx.annotation.VisibleForTesting
20 
21 /**
22  * A object used with [TableLogBuffer] to store changes in variables over time. Is recyclable.
23  *
24  * Each message represents a change to exactly 1 type, specified by [DataType].
25  *
26  * @property isInitial see [TableLogBuffer.logChange(String, Boolean, (TableRowLogger) -> Unit].
27  */
28 data class TableChange(
29     var timestamp: Long = 0,
30     private var columnPrefix: String = "",
31     private var columnName: String = "",
32     private var isInitial: Boolean = false,
33     private var type: DataType = DataType.EMPTY,
34     private var bool: Boolean = false,
35     private var int: Int? = null,
36     private var str: String? = null,
37 ) {
38     init {
39         // Truncate any strings that were passed into the constructor. [reset] and [set] will take
40         // care of the rest of the truncation.
41         this.columnPrefix = columnPrefix.take(MAX_STRING_LENGTH)
42         this.columnName = columnName.take(MAX_STRING_LENGTH)
43         this.str = str?.take(MAX_STRING_LENGTH)
44     }
45 
46     /** Resets to default values so that the object can be recycled. */
47     fun reset(timestamp: Long, columnPrefix: String, columnName: String, isInitial: Boolean) {
48         this.timestamp = timestamp
49         this.columnPrefix = columnPrefix.take(MAX_STRING_LENGTH)
50         this.columnName = columnName.take(MAX_STRING_LENGTH)
51         this.isInitial = isInitial
52         this.type = DataType.EMPTY
53         this.bool = false
54         this.int = 0
55         this.str = null
56     }
57 
58     /** Sets this to store a string change. */
59     fun set(value: String?) {
60         type = DataType.STRING
61         str = value?.take(MAX_STRING_LENGTH)
62     }
63 
64     /** Sets this to store a boolean change. */
65     fun set(value: Boolean) {
66         type = DataType.BOOLEAN
67         bool = value
68     }
69 
70     /** Sets this to store an int change. */
71     fun set(value: Int?) {
72         type = DataType.INT
73         int = value
74     }
75 
76     /** Updates this to store the same value as [change]. */
77     fun updateTo(change: TableChange) {
78         reset(change.timestamp, change.columnPrefix, change.columnName, change.isInitial)
79         when (change.type) {
80             DataType.STRING -> set(change.str)
81             DataType.INT -> set(change.int)
82             DataType.BOOLEAN -> set(change.bool)
83             DataType.EMPTY -> {}
84         }
85     }
86 
87     /** Returns true if this object has a change. */
88     fun hasData(): Boolean {
89         return columnName.isNotBlank() && type != DataType.EMPTY
90     }
91 
92     fun getName(): String {
93         return if (columnPrefix.isNotBlank()) {
94             "$columnPrefix.$columnName"
95         } else {
96             columnName
97         }
98     }
99 
100     fun getColumnName() = columnName
101 
102     fun getVal(): String {
103         val value =
104             when (type) {
105                 DataType.EMPTY -> null
106                 DataType.STRING -> str
107                 DataType.INT -> int
108                 DataType.BOOLEAN -> bool
109             }.toString()
110         return "${if (isInitial) IS_INITIAL_PREFIX else ""}$value"
111     }
112 
113     enum class DataType {
114         STRING,
115         BOOLEAN,
116         INT,
117         EMPTY,
118     }
119 
120     companion object {
121         @VisibleForTesting const val IS_INITIAL_PREFIX = "**"
122         // Don't allow any strings larger than this length so that we have a hard upper limit on the
123         // size of the data stored by the buffer.
124         @VisibleForTesting const val MAX_STRING_LENGTH = 500
125     }
126 }
127