1 /*
2  * Copyright 2018 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 package androidx.work
17 
18 import java.lang.IllegalArgumentException
19 import java.lang.reflect.Array
20 import java.util.HashMap
21 
22 /**
23  * An [InputMerger] that attempts to merge the inputs, creating arrays when necessary. For each
24  * input, we look at each key:
25  * * If this is the first time we encountered the key:
26  *     * If it's an array, put it in the output
27  *     * If it's a primitive, turn it into a size 1 array and put it in the output
28  * * Else (we have encountered the key before):
29  *     * If the value type matches the old value type:
30  *         * If they are arrays, concatenate them
31  *         * If they are primitives, turn them into a size 2 array
32  *     * Else if one is an array and the other is a primitive of that type:
33  *             * Make a longer array and concatenate them
34  *     * Else throw an [IllegalArgumentException] because the types don't match.
35  *
36  * If a value by a key is `null`, it is considered to have type `String`, because it is the only
37  * nullable typed allowed in [Data].
38  */
39 class ArrayCreatingInputMerger : InputMerger() {
40     @Suppress("DocumentExceptions")
mergenull41     override fun merge(inputs: List<Data>): Data {
42         val output = Data.Builder()
43         // values are always arrays
44         val mergedValues: MutableMap<String, Any> = HashMap()
45         for (input in inputs) {
46             for ((key, value) in input.keyValueMap) {
47                 val valueClass: Class<*> = value?.javaClass ?: String::class.java
48                 val existingValue = mergedValues[key]
49                 mergedValues[key] =
50                     if (existingValue == null) {
51                         // First time encountering this key.
52                         if (valueClass.isArray) {
53                             // Arrays carry over as-is.
54                             // if valueClass.isArray is true then value isn't null
55                             value as Any
56                         } else {
57                             // Primitives get turned into size 1 arrays.
58                             createArrayFor(value, valueClass)
59                         }
60                     } else {
61                         // We've encountered this key before.
62                         val existingValueClass: Class<*> = existingValue.javaClass
63                         when {
64                             existingValueClass == valueClass -> {
65                                 // The classes match; we can merge.
66                                 // both classes are arrays in this case => value isn't null
67                                 concatenateArrays(existingValue, value as Any)
68                             }
69                             existingValueClass.componentType == valueClass -> {
70                                 // We have an existing array of the same type.
71                                 concatenateArrayAndNonArray(existingValue, value, valueClass)
72                             }
73                             else -> throw IllegalArgumentException()
74                         }
75                     }
76             }
77         }
78         output.putAll(mergedValues)
79         return output.build()
80     }
81 
concatenateArraysnull82     private fun concatenateArrays(array1: Any, array2: Any): Any {
83         val length1 = Array.getLength(array1)
84         val length2 = Array.getLength(array2)
85         val newArray = Array.newInstance(array1.javaClass.componentType!!, length1 + length2)
86         System.arraycopy(array1, 0, newArray, 0, length1)
87         System.arraycopy(array2, 0, newArray, length1, length2)
88         return newArray
89     }
90 
concatenateArrayAndNonArraynull91     private fun concatenateArrayAndNonArray(array: Any, obj: Any?, valueClass: Class<*>): Any {
92         val arrayLength = Array.getLength(array)
93         val newArray = Array.newInstance(valueClass, arrayLength + 1)
94         System.arraycopy(array, 0, newArray, 0, arrayLength)
95         Array.set(newArray, arrayLength, obj)
96         return newArray
97     }
98 
createArrayFornull99     private fun createArrayFor(obj: Any?, valueClass: Class<*>): Any {
100         val newArray = Array.newInstance(valueClass, 1)
101         Array.set(newArray, 0, obj)
102         return newArray
103     }
104 }
105