1/*
2 * Copyright (C) 2017 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
17package android.os;
18
19import java.util.Arrays;
20import java.util.Collection;
21import java.util.Iterator;
22import java.util.List;
23import java.util.Map;
24import java.util.Objects;
25import java.util.stream.IntStream;
26
27/** @hide */
28public class HidlSupport {
29    /**
30     * Similar to Objects.deepEquals, but also take care of lists.
31     * Two objects of HIDL types are considered equal if:
32     * 1. Both null
33     * 2. Both non-null, and of the same class, and:
34     * 2.1 Both are primitive arrays / enum arrays, elements are equal using == check
35     * 2.2 Both are object arrays, elements are checked recursively
36     * 2.3 Both are Lists, elements are checked recursively
37     * 2.4 (If both are collections other than lists or maps, throw an error)
38     * 2.5 lft.equals(rgt) returns true
39     */
40    public static boolean deepEquals(Object lft, Object rgt) {
41        if (lft == rgt) {
42            return true;
43        }
44        if (lft == null || rgt == null) {
45            return false;
46        }
47
48        Class<?> lftClazz = lft.getClass();
49        Class<?> rgtClazz = rgt.getClass();
50        if (lftClazz != rgtClazz) {
51            return false;
52        }
53
54        if (lftClazz.isArray()) {
55            Class<?> lftElementType = lftClazz.getComponentType();
56            if (lftElementType != rgtClazz.getComponentType()) {
57                return false;
58            }
59
60            if (lftElementType.isPrimitive()) {
61                return Objects.deepEquals(lft, rgt);
62            }
63
64            Object[] lftArray = (Object[])lft;
65            Object[] rgtArray = (Object[])rgt;
66            return (lftArray.length == rgtArray.length) &&
67                   IntStream.range(0, lftArray.length).allMatch(
68                        i -> deepEquals(lftArray[i], rgtArray[i]));
69        }
70
71        if (lft instanceof List<?>) {
72            List<Object> lftList = (List<Object>)lft;
73            List<Object> rgtList = (List<Object>)rgt;
74            if (lftList.size() != rgtList.size()) {
75                return false;
76            }
77
78            Iterator<Object> lftIter = lftList.iterator();
79            return rgtList.stream()
80                    .allMatch(rgtElement -> deepEquals(lftIter.next(), rgtElement));
81        }
82
83        throwErrorIfUnsupportedType(lft);
84
85        return lft.equals(rgt);
86    }
87
88    /**
89     * Similar to Arrays.deepHashCode, but also take care of lists.
90     */
91    public static int deepHashCode(Object o) {
92        if (o == null) {
93            return 0;
94        }
95        Class<?> clazz = o.getClass();
96        if (clazz.isArray()) {
97            Class<?> elementType = clazz.getComponentType();
98            if (elementType.isPrimitive()) {
99                return primitiveArrayHashCode(o);
100            }
101            return Arrays.hashCode(Arrays.stream((Object[])o)
102                    .mapToInt(element -> deepHashCode(element))
103                    .toArray());
104        }
105
106        if (o instanceof List<?>) {
107            return Arrays.hashCode(((List<Object>)o).stream()
108                    .mapToInt(element -> deepHashCode(element))
109                    .toArray());
110        }
111
112        throwErrorIfUnsupportedType(o);
113
114        return o.hashCode();
115    }
116
117    private static void throwErrorIfUnsupportedType(Object o) {
118        if (o instanceof Collection<?> && !(o instanceof List<?>)) {
119            throw new UnsupportedOperationException(
120                    "Cannot check equality on collections other than lists: " +
121                    o.getClass().getName());
122        }
123
124        if (o instanceof Map<?, ?>) {
125            throw new UnsupportedOperationException(
126                    "Cannot check equality on maps");
127        }
128    }
129
130    private static int primitiveArrayHashCode(Object o) {
131        Class<?> elementType = o.getClass().getComponentType();
132        if (elementType == boolean.class) {
133            return Arrays.hashCode(((boolean[])o));
134        }
135        if (elementType == byte.class) {
136            return Arrays.hashCode(((byte[])o));
137        }
138        if (elementType == char.class) {
139            return Arrays.hashCode(((char[])o));
140        }
141        if (elementType == double.class) {
142            return Arrays.hashCode(((double[])o));
143        }
144        if (elementType == float.class) {
145            return Arrays.hashCode(((float[])o));
146        }
147        if (elementType == int.class) {
148            return Arrays.hashCode(((int[])o));
149        }
150        if (elementType == long.class) {
151            return Arrays.hashCode(((long[])o));
152        }
153        if (elementType == short.class) {
154            return Arrays.hashCode(((short[])o));
155        }
156        // Should not reach here.
157        throw new UnsupportedOperationException();
158    }
159}
160