1 /* 2 * Copyright (C) 2021 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.onboarding.bedsteadonboarding.queryable 18 19 import android.os.Parcel 20 import android.os.Parcelable 21 import com.android.onboarding.bedsteadonboarding.contractutils.ContractUtils 22 import com.android.onboarding.nodes.OnboardingEvent 23 import com.android.onboarding.nodes.OnboardingGraphNode 24 import com.android.onboarding.nodes.OnboardingGraphLog 25 import com.android.queryable.Queryable 26 import com.android.queryable.queries.BooleanQueryHelper 27 import com.android.queryable.queries.LongQueryHelper 28 import com.android.queryable.queries.StringQueryHelper 29 import java.time.Instant 30 import kotlin.collections.Collection 31 32 /** 33 * Implementation of [NodeQuery]. 34 */ 35 class NodeQueryHelper<E : Queryable> : NodeQuery<E> { 36 @Transient private lateinit var query: E 37 38 private val idQueryHelper: LongQueryHelper<E> 39 private val componentQueryHelper: ComponentQueryHelper<E> 40 private val nameQueryHelper: StringQueryHelper<E> 41 private val startedQueryHelper: BooleanQueryHelper<E> 42 private val finishedQueryHelper: BooleanQueryHelper<E> 43 private val failedQueryHelper: BooleanQueryHelper<E> 44 private val happenedBeforeQueryHelpers: HashMap<OnboardingGraphNode, NodeSequenceQueryHelper<E>> 45 private val happenedAfterQueryHelpers: HashMap<OnboardingGraphNode, NodeSequenceQueryHelper<E>> 46 47 constructor(query: E) { 48 this.query = query 49 idQueryHelper = LongQueryHelper(query) 50 componentQueryHelper = ComponentQueryHelper(query) 51 nameQueryHelper = StringQueryHelper(query) 52 startedQueryHelper = BooleanQueryHelper(query) 53 finishedQueryHelper = BooleanQueryHelper(query) 54 failedQueryHelper = BooleanQueryHelper(query) 55 happenedBeforeQueryHelpers = HashMap() 56 happenedAfterQueryHelpers = HashMap() 57 } 58 59 constructor(parcel: Parcel) { 60 idQueryHelper = parcel.readParcelable(NodeQueryHelper::class.java.classLoader) 61 as LongQueryHelper<E>? ?: throw IllegalStateException() 62 componentQueryHelper = parcel.readParcelable(NodeQueryHelper::class.java.classLoader) 63 as ComponentQueryHelper<E>? ?: throw IllegalStateException() 64 nameQueryHelper = parcel.readParcelable(NodeQueryHelper::class.java.classLoader) 65 as StringQueryHelper<E>? ?: throw IllegalStateException() 66 startedQueryHelper = parcel.readParcelable(NodeQueryHelper::class.java.classLoader) 67 as BooleanQueryHelper<E>? ?: throw IllegalStateException() 68 finishedQueryHelper = parcel.readParcelable(NodeQueryHelper::class.java.classLoader) 69 as BooleanQueryHelper<E>? ?: throw IllegalStateException() 70 failedQueryHelper = parcel.readParcelable(NodeQueryHelper::class.java.classLoader) 71 as BooleanQueryHelper<E>? ?: throw IllegalStateException() 72 happenedBeforeQueryHelpers = parcel.readParcelable(NodeQueryHelper::class.java.classLoader) 73 as HashMap<OnboardingGraphNode, NodeSequenceQueryHelper<E>>? ?: throw IllegalStateException() 74 happenedAfterQueryHelpers = parcel.readParcelable(NodeQueryHelper::class.java.classLoader) 75 as HashMap<OnboardingGraphNode, NodeSequenceQueryHelper<E>>? ?: throw IllegalStateException() 76 } 77 idnull78 override fun id() = idQueryHelper 79 80 override fun name() = nameQueryHelper 81 82 override fun component() = componentQueryHelper 83 84 override fun isStarted() = startedQueryHelper 85 86 override fun isFinished() = finishedQueryHelper 87 88 override fun isFailed() = failedQueryHelper 89 90 override fun happenedBefore(node: OnboardingGraphNode): NodeSequenceQuery<E> { 91 if (happenedBeforeQueryHelpers.containsKey(node)) { 92 return happenedBeforeQueryHelpers[node]!! 93 } 94 95 val nodeSequenceQuery = NodeSequenceQueryHelper(query) 96 nodeSequenceQuery.nodeToCompareWith = node 97 happenedBeforeQueryHelpers[node] = nodeSequenceQuery 98 99 return nodeSequenceQuery 100 } 101 happenedAfternull102 override fun happenedAfter(node: OnboardingGraphNode): NodeSequenceQuery<E> { 103 if (happenedAfterQueryHelpers.containsKey(node)) { 104 return happenedAfterQueryHelpers[node]!! 105 } 106 107 val nodeSequenceQuery = NodeSequenceQueryHelper(query) 108 nodeSequenceQuery.nodeToCompareWith = node 109 happenedAfterQueryHelpers[node] = nodeSequenceQuery 110 111 return nodeSequenceQuery 112 } 113 describeQuerynull114 override fun describeQuery(fieldName: String?): String { 115 val queryStrings = ArrayList<String>() 116 117 queryStrings.add(idQueryHelper.describeQuery("$fieldName.id")) 118 queryStrings.add(componentQueryHelper.describeQuery("$fieldName.component")) 119 queryStrings.add(nameQueryHelper.describeQuery("$fieldName.name")) 120 queryStrings.add(startedQueryHelper.describeQuery("$fieldName.started")) 121 queryStrings.add(finishedQueryHelper.describeQuery("$fieldName.finished")) 122 queryStrings.add(failedQueryHelper.describeQuery("$fieldName.failed")) 123 124 for (query in happenedBeforeQueryHelpers.values) { 125 queryStrings.add(query.describeQuery("$fieldName.happenedBefore")!!) 126 } 127 128 for (query in happenedAfterQueryHelpers.values) { 129 queryStrings.add(query.describeQuery("$fieldName.happenedAfter")!!) 130 } 131 132 return Queryable.joinQueryStrings(queryStrings) 133 } 134 isEmptyQuerynull135 override fun isEmptyQuery(): Boolean { 136 val isEmptyQuery = Queryable.isEmptyQuery(idQueryHelper) && 137 Queryable.isEmptyQuery(componentQueryHelper) && 138 Queryable.isEmptyQuery(nameQueryHelper) && 139 Queryable.isEmptyQuery(startedQueryHelper) && 140 Queryable.isEmptyQuery(finishedQueryHelper) && 141 Queryable.isEmptyQuery(failedQueryHelper) 142 143 if (!isEmptyQuery) { 144 return false 145 } 146 147 for (query in happenedBeforeQueryHelpers.values) { 148 if (!Queryable.isEmptyQuery(query)) { 149 return false 150 } 151 } 152 153 for (query in happenedAfterQueryHelpers.values) { 154 if (!Queryable.isEmptyQuery(query)) { 155 return false 156 } 157 } 158 159 return true 160 } 161 describeContentsnull162 override fun describeContents(): Int { 163 return 0 164 } 165 writeToParcelnull166 override fun writeToParcel(out: Parcel, flags: Int) { 167 out.writeParcelable(idQueryHelper, flags) 168 out.writeParcelable(componentQueryHelper, flags) 169 out.writeParcelable(nameQueryHelper, flags) 170 out.writeParcelable(startedQueryHelper, flags) 171 out.writeParcelable(finishedQueryHelper, flags) 172 out.writeParcelable(failedQueryHelper, flags) 173 out.writeMap(happenedBeforeQueryHelpers) 174 out.writeMap(happenedAfterQueryHelpers) 175 } 176 matchesnull177 override fun matches(node: OnboardingGraphNode): Boolean { 178 val matches = idQueryHelper.matches(node.id) && 179 componentQueryHelper.matches(node.component) && 180 nameQueryHelper.matches(node.name) && 181 startedQueryHelper.matches(isNodeStarted(node)) && 182 finishedQueryHelper.matches(isNodeFinished(node)) && 183 failedQueryHelper.matches(isNodeFailed(node)) 184 185 if (!matches) { 186 return false 187 } 188 189 for (query in happenedBeforeQueryHelpers.values) { 190 if (!query.matches(isHappenedBefore(node))) { 191 return false 192 } 193 } 194 195 for (query in happenedAfterQueryHelpers.values) { 196 if (!query.matches(isHappenedAfter(node))) { 197 return false 198 } 199 } 200 201 return true 202 } 203 isNodeStartednull204 private fun isNodeStarted(node: OnboardingGraphNode): Boolean { 205 return node.start < Instant.now() 206 } 207 isNodeFinishednull208 private fun isNodeFinished(node: OnboardingGraphNode): Boolean { 209 return node.end < Instant.now() && 210 !isAnotherNodeAttemptedToBeExecutedForResult( 211 node.events, ContractUtils.getContractIdentifier(node.component!!.name, node.name)) && 212 node.outgoingEdgesOfValidNodes.isEmpty() 213 } 214 isNodeFailednull215 private fun isNodeFailed(node: OnboardingGraphNode): Boolean { 216 return node.isFailed 217 } 218 isHappenedBeforenull219 private fun isHappenedBefore(node: OnboardingGraphNode): Boolean { 220 if (Queryable.isEmptyQuery(happenedBeforeQueryHelpers[node])) { 221 return true 222 } 223 return node.start.isBefore(happenedBeforeQueryHelpers.getValue(node).nodeToCompareWith!!.start) 224 } 225 isHappenedAfternull226 private fun isHappenedAfter(node: OnboardingGraphNode): Boolean { 227 if (Queryable.isEmptyQuery(happenedAfterQueryHelpers[node])) { 228 return true 229 } 230 return node.start.isAfter(happenedAfterQueryHelpers.getValue(node).nodeToCompareWith!!.start) 231 } 232 isAnotherNodeAttemptedToBeExecutedForResultnull233 private fun isAnotherNodeAttemptedToBeExecutedForResult( 234 events: Collection<OnboardingGraphLog.OnboardingEventDelegate>, 235 contractIdentifierOfNode: String 236 ): Boolean { 237 return events.any { 238 val event = it.source 239 (event is OnboardingEvent.ActivityNodeStartExecuteSynchronously) && 240 (ContractUtils.getContractIdentifier(event.nodeComponent, event.nodeName) != 241 contractIdentifierOfNode) 242 } 243 } 244 245 companion object CREATOR : Parcelable.Creator<NodeQueryHelper<Queryable>> { createFromParcelnull246 override fun createFromParcel(parcel: Parcel): NodeQueryHelper<Queryable> { 247 return NodeQueryHelper(parcel) 248 } 249 newArraynull250 override fun newArray(size: Int): Array<NodeQueryHelper<Queryable>?> { 251 return arrayOfNulls(size) 252 } 253 } 254 } 255