/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.libraries.pcc.chronicle.api.operation /** * An [Operation] is a [named][name] mapping from [inputType] to [outputType] via the result type: * [Action]. * * Operations are used to apply conditional usage filters/redactions/truncations/etc. * * An operation is considered equal to another operation if and only if its [name], [inputType], and * [outputType] are equal. Thus, it is important that functionally-different operations have * different names or input/output types. */ abstract class Operation( val name: String, val inputType: Class, val outputType: Class, ) : (A) -> Action { /** * Calculates an [Action] of type [B] from the input [value]. This is the body of the operation. */ abstract override fun invoke(value: A): Action // Generated by intellij override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Operation<*, *>) return false if (name != other.name) return false if (inputType != other.inputType) return false if (outputType != other.outputType) return false return true } // Generated by intellij override fun hashCode(): Int { var result = name.hashCode() result = 31 * result + inputType.hashCode() result = 31 * result + outputType.hashCode() return result } companion object { /** * Creates an [Operation] that can never return an [Action.Update], only omissions or throwing * [Actions][Action]. The [block] may return `null`, in which case the operation will cause * no-change. */ inline fun createNonUpdating( name: String, crossinline block: (value: A) -> Action?, ): Operation { return object : Operation(name, A::class.java, Nothing::class.java) { override fun invoke(value: A): Action { @Suppress("UNCHECKED_CAST") // It's a no-op when the cast occurs, so nothing is awry. return block(value) ?: Action.Update(value) as Action } } } /** Creates an [Operation] mapping input values of type [A] to [Actions][Action] of type [A]. */ inline fun create( name: String, crossinline block: (value: A) -> Action, ): Operation { return object : Operation(name, A::class.java, A::class.java) { override fun invoke(value: A): Action = block(value) } } /** * Creates an [Operation] mapping input values of type [A] to [Actions][Action] of type [B]. * * It's important to note that transform operations are not usually applicable to conditional * usage from policies, unless B is a subclass of A (example: input is nullable and output * should be non-null). */ inline fun createTransform( name: String, crossinline block: (value: A) -> Action, ): Operation { return object : Operation(name, A::class.java, B::class.java) { override fun invoke(value: A): Action = block(value) } } } }