• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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