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 
17 package androidx.work;
18 
19 import android.support.annotation.NonNull;
20 
21 import java.lang.reflect.Array;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 
26 /**
27  * An {@link InputMerger} that attempts to merge the various inputs.  For each input, we look at
28  * each key:
29  * <p><ul>
30  * <li>If this is the first time we encountered the key</li>
31  *   <ul>
32  *   <li>If it's an array, put it in the output</li>
33  *   <li>If it's a primitive, turn it into a size 1 array and put it in the output</li>
34  *   </ul>
35  * <li>Else</li>
36  *   <ul>
37  *   <li>If the value type matches the old value type</li>
38  *     <ul>
39  *     <li>If they are arrays, concatenate them</li>
40  *     <li>If they are primitives, turn them into a size 2 array</li>
41  *     </ul>
42  *   <li>Else if one is an array and the other is a primitive</li>
43  *     <ul>
44  *     <li>Make a longer array and concatenate them</li>
45  *     </ul>
46  *   <li>Else throw an {@link IllegalArgumentException}</li>
47  *   </ul>
48  * </ul>
49  */
50 
51 public final class ArrayCreatingInputMerger extends InputMerger {
52 
53     @Override
merge(@onNull List<Data> inputs)54     public @NonNull Data merge(@NonNull List<Data> inputs) {
55         Data.Builder output = new Data.Builder();
56         Map<String, Object> mergedValues = new HashMap<>();
57 
58         for (Data input : inputs) {
59             for (Map.Entry<String, Object> entry : input.getKeyValueMap().entrySet()) {
60                 String key = entry.getKey();
61                 Object value = entry.getValue();
62                 Class valueClass = value.getClass();
63                 Object mergedValue = null;
64 
65                 if (!mergedValues.containsKey(key)) {
66                     // First time encountering this key.
67                     if (valueClass.isArray()) {
68                         // Arrays carry over as-is.
69                         mergedValue = value;
70                     } else {
71                         // Primitives get turned into size 1 arrays.
72                         mergedValue = createArrayFor(value);
73                     }
74                 } else {
75                     // We've encountered this key before.
76                     Object existingValue = mergedValues.get(key);
77                     Class existingValueClass = existingValue.getClass();
78 
79                     if (existingValueClass.equals(valueClass)) {
80                         // The classes match; we can merge.
81                         if (existingValueClass.isArray()) {
82                             mergedValue = concatenateArrays(existingValue, value);
83                         } else {
84                             mergedValue = concatenateNonArrays(existingValue, value);
85                         }
86                     } else if (existingValueClass.isArray()
87                             && existingValueClass.getComponentType().equals(valueClass)) {
88                         // We have an existing array of the same type.
89                         mergedValue = concatenateArrayAndNonArray(existingValue, value);
90                     } else if (valueClass.isArray()
91                             && valueClass.getComponentType().equals(existingValueClass)) {
92                         // We have an existing array of the same type.
93                         mergedValue = concatenateArrayAndNonArray(value, existingValue);
94                     } else {
95                         throw new IllegalArgumentException();
96                     }
97                 }
98 
99                 mergedValues.put(key, mergedValue);
100             }
101         }
102 
103         output.putAll(mergedValues);
104         return output.build();
105     }
106 
concatenateArrays(Object array1, Object array2)107     private Object concatenateArrays(Object array1, Object array2) {
108         int length1 = Array.getLength(array1);
109         int length2 = Array.getLength(array2);
110         Object newArray = Array.newInstance(array1.getClass().getComponentType(),
111                 length1 + length2);
112         System.arraycopy(array1, 0, newArray, 0, length1);
113         System.arraycopy(array2, 0, newArray, length1, length2);
114         return newArray;
115     }
116 
concatenateNonArrays(Object obj1, Object obj2)117     private Object concatenateNonArrays(Object obj1, Object obj2) {
118         Object newArray = Array.newInstance(obj1.getClass(), 2);
119         Array.set(newArray, 0, obj1);
120         Array.set(newArray, 1, obj2);
121         return newArray;
122     }
123 
concatenateArrayAndNonArray(Object array, Object obj)124     private Object concatenateArrayAndNonArray(Object array, Object obj) {
125         int arrayLength = Array.getLength(array);
126         Object newArray = Array.newInstance(obj.getClass(), arrayLength + 1);
127         System.arraycopy(array, 0, newArray, 0, arrayLength);
128         Array.set(newArray, arrayLength, obj);
129         return newArray;
130     }
131 
createArrayFor(Object obj)132     private Object createArrayFor(Object obj) {
133         Object newArray = Array.newInstance(obj.getClass(), 1);
134         Array.set(newArray, 0, obj);
135         return newArray;
136     }
137 }
138