CollectionUtils.java revision 4b7c919e725bc87bea82ab2c2370f094a049b8cf
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 com.android.internal.util;
18
19import static com.android.internal.util.ArrayUtils.isEmpty;
20
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.util.ArraySet;
24import android.util.ExceptionUtils;
25
26import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
27
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.Collections;
31import java.util.List;
32import java.util.Set;
33import java.util.function.Function;
34import java.util.stream.Stream;
35
36/**
37 * Utility methods for dealing with (typically {@code Nullable}) {@link Collection}s
38 *
39 * Unless a method specifies otherwise, a null value for a collection is treated as an empty
40 * collection of that type.
41 */
42public class CollectionUtils {
43    private CollectionUtils() { /* cannot be instantiated */ }
44
45    /**
46     * Returns a list of items from the provided list that match the given condition.
47     *
48     * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
49     * {@link Stream} instance
50     */
51    public static @NonNull <T> List<T> filter(@Nullable List<T> list,
52            java.util.function.Predicate<? super T> predicate) {
53        ArrayList<T> result = null;
54        for (int i = 0; i < size(list); i++) {
55            final T item = list.get(i);
56            if (predicate.test(item)) {
57                result = ArrayUtils.add(result, item);
58            }
59        }
60        return emptyIfNull(result);
61    }
62
63    /**
64     * @see #filter(List, java.util.function.Predicate)
65     */
66    public static @NonNull <T> Set<T> filter(@Nullable Set<T> set,
67            java.util.function.Predicate<? super T> predicate) {
68        if (set == null || set.size() == 0) return Collections.emptySet();
69        ArraySet<T> result = null;
70        if (set instanceof ArraySet) {
71            ArraySet<T> arraySet = (ArraySet<T>) set;
72            int size = arraySet.size();
73            for (int i = 0; i < size; i++) {
74                final T item = arraySet.valueAt(i);
75                if (predicate.test(item)) {
76                    result = ArrayUtils.add(result, item);
77                }
78            }
79        } else {
80            for (T item : set) {
81                if (predicate.test(item)) {
82                    result = ArrayUtils.add(result, item);
83                }
84            }
85        }
86        return emptyIfNull(result);
87    }
88
89    /**
90     * Returns a list of items resulting from applying the given function to each element of the
91     * provided list.
92     *
93     * The resulting list will have the same {@link #size} as the input one.
94     *
95     * This is similar to {@link Stream#map} but without the overhead of creating an intermediate
96     * {@link Stream} instance
97     */
98    public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
99            Function<? super I, ? extends O> f) {
100        if (isEmpty(cur)) return Collections.emptyList();
101        final ArrayList<O> result = new ArrayList<>();
102        for (int i = 0; i < cur.size(); i++) {
103            result.add(f.apply(cur.get(i)));
104        }
105        return result;
106    }
107
108    /**
109     * @see #map(List, Function)
110     */
111    public static @NonNull <I, O> Set<O> map(@Nullable Set<I> cur,
112            Function<? super I, ? extends O> f) {
113        if (isEmpty(cur)) return Collections.emptySet();
114        ArraySet<O> result = new ArraySet<>();
115        if (cur instanceof ArraySet) {
116            ArraySet<I> arraySet = (ArraySet<I>) cur;
117            int size = arraySet.size();
118            for (int i = 0; i < size; i++) {
119                result.add(f.apply(arraySet.valueAt(i)));
120            }
121        } else {
122            for (I item : cur) {
123                result.add(f.apply(item));
124            }
125        }
126        return result;
127    }
128
129    /**
130     * {@link #map(List, Function)} + {@link #filter(List, java.util.function.Predicate)}
131     *
132     * Calling this is equivalent (but more memory efficient) to:
133     *
134     * {@code
135     *      filter(
136     *          map(cur, f),
137     *          i -> { i != null })
138     * }
139     */
140    public static @NonNull <I, O> List<O> mapNotNull(@Nullable List<I> cur,
141            Function<? super I, ? extends O> f) {
142        if (isEmpty(cur)) return Collections.emptyList();
143        final ArrayList<O> result = new ArrayList<>();
144        for (int i = 0; i < cur.size(); i++) {
145            O transformed = f.apply(cur.get(i));
146            if (transformed != null) {
147                result.add(transformed);
148            }
149        }
150        return result;
151    }
152
153    /**
154     * Returns the given list, or an immutable empty list if the provided list is null
155     *
156     * This can be used to guarantee null-safety without paying the price of extra allocations
157     *
158     * @see Collections#emptyList
159     */
160    public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
161        return cur == null ? Collections.emptyList() : cur;
162    }
163
164    /**
165     * Returns the given set, or an immutable empty set if the provided set is null
166     *
167     * This can be used to guarantee null-safety without paying the price of extra allocations
168     *
169     * @see Collections#emptySet
170     */
171    public static @NonNull <T> Set<T> emptyIfNull(@Nullable Set<T> cur) {
172        return cur == null ? Collections.emptySet() : cur;
173    }
174
175    /**
176     * Returns the size of the given list, or 0 if the list is null
177     */
178    public static int size(@Nullable Collection<?> cur) {
179        return cur != null ? cur.size() : 0;
180    }
181
182    /**
183     * Returns the elements of the given list that are of type {@code c}
184     */
185    public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
186        if (isEmpty(list)) return Collections.emptyList();
187        ArrayList<T> result = null;
188        for (int i = 0; i < list.size(); i++) {
189            final Object item = list.get(i);
190            if (c.isInstance(item)) {
191                result = ArrayUtils.add(result, (T) item);
192            }
193        }
194        return emptyIfNull(result);
195    }
196
197    /**
198     * Returns whether there exists at least one element in the list for which
199     * condition {@code predicate} is true
200     */
201    public static <T> boolean any(@Nullable List<T> items,
202            java.util.function.Predicate<T> predicate) {
203        return find(items, predicate) != null;
204    }
205
206    /**
207     * Returns the first element from the list for which
208     * condition {@code predicate} is true, or null if there is no such element
209     */
210    public static @Nullable <T> T find(@Nullable List<T> items,
211            java.util.function.Predicate<T> predicate) {
212        if (isEmpty(items)) return null;
213        for (int i = 0; i < items.size(); i++) {
214            final T item = items.get(i);
215            if (predicate.test(item)) return item;
216        }
217        return null;
218    }
219
220    /**
221     * Similar to {@link List#add}, but with support for list values of {@code null} and
222     * {@link Collections#emptyList}
223     */
224    public static @NonNull <T> List<T> add(@Nullable List<T> cur, T val) {
225        if (cur == null || cur == Collections.emptyList()) {
226            cur = new ArrayList<>();
227        }
228        cur.add(val);
229        return cur;
230    }
231
232    /**
233     * @see #add(List, Object)
234     */
235    public static @NonNull <T> Set<T> add(@Nullable Set<T> cur, T val) {
236        if (cur == null || cur == Collections.emptySet()) {
237            cur = new ArraySet<>();
238        }
239        cur.add(val);
240        return cur;
241    }
242
243    /**
244     * Similar to {@link List#remove}, but with support for list values of {@code null} and
245     * {@link Collections#emptyList}
246     */
247    public static @NonNull <T> List<T> remove(@Nullable List<T> cur, T val) {
248        if (isEmpty(cur)) {
249            return emptyIfNull(cur);
250        }
251        cur.remove(val);
252        return cur;
253    }
254
255    /**
256     * @see #remove(List, Object)
257     */
258    public static @NonNull <T> Set<T> remove(@Nullable Set<T> cur, T val) {
259        if (isEmpty(cur)) {
260            return emptyIfNull(cur);
261        }
262        cur.remove(val);
263        return cur;
264    }
265
266    /**
267     * @return a list that will not be affected by mutations to the given original list.
268     */
269    public static @NonNull <T> List<T> copyOf(@Nullable List<T> cur) {
270        return isEmpty(cur) ? Collections.emptyList() : new ArrayList<>(cur);
271    }
272
273    /**
274     * @return a list that will not be affected by mutations to the given original list.
275     */
276    public static @NonNull <T> Set<T> copyOf(@Nullable Set<T> cur) {
277        return isEmpty(cur) ? Collections.emptySet() : new ArraySet<>(cur);
278    }
279
280    /**
281     * Applies {@code action} to each element in {@code cur}
282     *
283     * This avoids creating an iterator if the given set is an {@link ArraySet}
284     */
285    public static <T> void forEach(@Nullable Set<T> cur, @Nullable ThrowingConsumer<T> action) {
286        if (cur == null || action == null) return;
287        int size = cur.size();
288        if (size == 0) return;
289        try {
290            if (cur instanceof ArraySet) {
291                ArraySet<T> arraySet = (ArraySet<T>) cur;
292                for (int i = 0; i < size; i++) {
293                    action.acceptOrThrow(arraySet.valueAt(i));
294                }
295            } else {
296                for (T t : cur) {
297                    action.acceptOrThrow(t);
298                }
299            }
300        } catch (Exception e) {
301            throw ExceptionUtils.propagate(e);
302        }
303    }
304}
305