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.libraries.pcc.chronicle.api.optics 18 19 import com.android.libraries.pcc.chronicle.api.operation.Action 20 21 typealias ListTraversal<A> = Traversal<List<A>, List<A>, A, A> 22 23 typealias ListMapping<A, B> = Traversal<List<A>, List<B>, A, B> 24 25 /** 26 * A [Traversal] is a functional optic intended for getting or updating 0 to N items. 27 * 28 * In Chronicle it serves as a way to apply updates to lists, sets, and other collection types. 29 */ 30 abstract class Traversal<S, T, A, B>( 31 val sourceAccessPath: OpticalAccessPath, 32 val targetAccessPath: OpticalAccessPath, 33 val sourceEntityType: Class<out S>, 34 val targetEntityType: Class<out T>, 35 val sourceFieldType: Class<out A>, 36 val targetFieldType: Class<out B> 37 ) { 38 /** Whether or not the [Traversal] is actually monomorphic. */ 39 val isMonomorphic: Boolean = 40 sourceEntityType == targetEntityType && sourceFieldType == targetFieldType 41 42 /** 43 * Given an [entity] of type [S], returns a [Sequence] containing every targeted item of type [A] 44 * within the entity. 45 */ everynull46 abstract fun every(entity: S): Sequence<A> 47 48 /** 49 * Given an [entity] of type [S], and a modifier mapping from [A] to [B], returns an instance of 50 * [T] where each member of [entity] was converted from [A] to [B]. 51 */ 52 abstract fun modify(entity: S, modifier: (A) -> B): T 53 54 /** 55 * Similar to [modify] but the result type is an [Action], which allows for more nuanced 56 * adjustment to the source data. 57 */ 58 abstract fun modifyWithAction(entity: S, modifier: (value: A) -> Action<out B>): Action<out T> 59 60 /** Lift a [modifier] function mapping from [A] -> [B] to the scope of [S] an [T]. */ 61 fun lift(modifier: (A) -> B): (S) -> T = { modify(it, modifier) } 62 63 /** Compose this [Traversal] with another [Traversal]. */ 64 @Suppress("UNCHECKED_CAST") // We do check, actually. composenull65 infix fun <AIn : A, BIn : B, NewA : Any?, NewB : Any?> compose( 66 other: Traversal<AIn, BIn, NewA, NewB> 67 ): Traversal<S, T, NewA, NewB> { 68 require(this canCompose other) { "$this cannot compose with $other" } 69 70 return object : 71 Traversal<S, T, NewA, NewB>( 72 sourceAccessPath = sourceAccessPath compose other.sourceAccessPath, 73 targetAccessPath = targetAccessPath compose other.targetAccessPath, 74 sourceEntityType = sourceEntityType, 75 targetEntityType = targetEntityType, 76 sourceFieldType = other.sourceFieldType, 77 targetFieldType = other.targetFieldType 78 ) { 79 override fun every(entity: S): Sequence<NewA> = 80 this@Traversal.every(entity).flatMap { other.every(it as AIn) } 81 82 @Suppress("ALWAYS_NULL", "USELESS_CAST") 83 // The [B] type variable must be nullable, but alas: type-erasure. 84 override fun modify(entity: S, modifier: (NewA) -> NewB): T = 85 this@Traversal.modify(entity) { 86 if (it != null) { 87 other.modify(it as AIn, modifier) 88 } else { 89 // If `it` is null, just return it - since `other` can't be a 90 it as B 91 } 92 } 93 94 @Suppress("ALWAYS_NULL", "USELESS_CAST") 95 // The [B] type variable must be nullable, but alas: type-erasure. 96 override fun modifyWithAction( 97 entity: S, 98 modifier: (value: NewA) -> Action<out NewB> 99 ): Action<out T> { 100 return this@Traversal.modifyWithAction(entity) { 101 if (it != null) { 102 other.modifyWithAction(it as AIn, modifier) 103 } else { 104 Action.Update(it as B) 105 } 106 } 107 } 108 } 109 } 110 111 /** 112 * Returns whether or not the receiving [Traversal] can compose with the [other] [Traversal]. 113 * 114 * A [Traversal] can compose with another if and only if the original entity type of the [other] 115 * can be assigned to the original field of the entity targeted by the LHS ***and*** if the 116 * modified entity type of the [other] can be assigned to the modified field of the entity 117 * targeted by the LHS. 118 */ canComposenull119 infix fun canCompose(other: Traversal<*, *, *, *>): Boolean = 120 sourceFieldType.isAssignableFrom(other.sourceEntityType) && 121 targetFieldType.isAssignableFrom(other.targetEntityType) 122 123 override fun toString(): String { 124 if (isMonomorphic) return "Traversal($sourceAccessPath)" 125 return "Traversal($sourceAccessPath -> $targetAccessPath)" 126 } 127 128 companion object { 129 /** An [OpticalAccessPath] representing a [Traversal] over a [List]. */ 130 val LIST_TRAVERSAL_ACCESS_PATH = OpticalAccessPath("List", "forEach") 131 132 /** Creates a [Traversal] operating on a [List] of elements of type [A]. */ listnull133 inline fun <reified A : Any> list(): ListTraversal<A> { 134 val path = 135 OpticalAccessPath("List<${A::class.java.simpleName}>") compose LIST_TRAVERSAL_ACCESS_PATH 136 val dummyEntity = emptyList<A>() 137 return object : 138 ListTraversal<A>( 139 sourceAccessPath = path, 140 targetAccessPath = path, 141 sourceEntityType = dummyEntity::class.java, 142 targetEntityType = dummyEntity::class.java, 143 sourceFieldType = A::class.java, 144 targetFieldType = A::class.java 145 ) { 146 override fun every(entity: List<A>): Sequence<A> = entity.asSequence() 147 148 override fun modify(entity: List<A>, modifier: (A) -> A): List<A> = entity.map(modifier) 149 150 override fun modifyWithAction( 151 entity: List<A>, 152 modifier: (value: A) -> Action<out A> 153 ): Action<out List<A>> { 154 return Action.Update( 155 entity.mapNotNull { 156 when (val res = modifier(it)) { 157 Action.OmitFromParent -> null 158 is Action.Update -> res.newValue 159 // Immediately bubble-up root omissions and throwing results. 160 is Action.OmitFromRoot -> return res 161 is Action.Throw -> return res 162 } 163 } 164 ) 165 } 166 } 167 } 168 169 /** 170 * Creates a [Traversal] operating on a [List] of elements of type [A], mapping them to type [B] 171 * . 172 */ listMapnull173 inline fun <reified A : Any, reified B : Any> listMap(): ListMapping<A, B> { 174 val sourcePath = 175 OpticalAccessPath("List<${A::class.java.simpleName}>") compose LIST_TRAVERSAL_ACCESS_PATH 176 val targetPath = 177 OpticalAccessPath("List<${B::class.java.simpleName}>") compose LIST_TRAVERSAL_ACCESS_PATH 178 val dummySource = emptyList<A>() 179 val dummyTarget = emptyList<B>() 180 return object : 181 Traversal<List<A>, List<B>, A, B>( 182 sourceAccessPath = sourcePath, 183 targetAccessPath = targetPath, 184 sourceEntityType = dummySource::class.java, 185 targetEntityType = dummyTarget::class.java, 186 sourceFieldType = A::class.java, 187 targetFieldType = B::class.java 188 ) { 189 override fun every(entity: List<A>): Sequence<A> = entity.asSequence() 190 191 override fun modify(entity: List<A>, modifier: (A) -> B): List<B> = entity.map(modifier) 192 193 override fun modifyWithAction( 194 entity: List<A>, 195 modifier: (value: A) -> Action<out B> 196 ): Action<out List<B>> { 197 return Action.Update( 198 entity.mapNotNull { 199 when (val res = modifier(it)) { 200 Action.OmitFromParent -> null 201 is Action.Update -> res.newValue 202 // Immediately bubble-up root omissions and throwing results. 203 is Action.OmitFromRoot -> return res 204 is Action.Throw -> return res 205 } 206 } 207 ) 208 } 209 } 210 } 211 } 212 } 213