/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArraySet; import android.util.ExceptionUtils; import com.android.internal.util.FunctionalUtils.ThrowingConsumer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; /** * Utility methods for dealing with (typically {@code Nullable}) {@link Collection}s * * Unless a method specifies otherwise, a null value for a collection is treated as an empty * collection of that type. */ public class CollectionUtils { private CollectionUtils() { /* cannot be instantiated */ } /** * Returns a list of items from the provided list that match the given condition. * * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate * {@link Stream} instance */ public static @NonNull List filter(@Nullable List list, java.util.function.Predicate predicate) { ArrayList result = null; for (int i = 0; i < size(list); i++) { final T item = list.get(i); if (predicate.test(item)) { result = ArrayUtils.add(result, item); } } return emptyIfNull(result); } /** * @see #filter(List, java.util.function.Predicate) */ public static @NonNull Set filter(@Nullable Set set, java.util.function.Predicate predicate) { if (set == null || set.size() == 0) return Collections.emptySet(); ArraySet result = null; if (set instanceof ArraySet) { ArraySet arraySet = (ArraySet) set; int size = arraySet.size(); for (int i = 0; i < size; i++) { final T item = arraySet.valueAt(i); if (predicate.test(item)) { result = ArrayUtils.add(result, item); } } } else { for (T item : set) { if (predicate.test(item)) { result = ArrayUtils.add(result, item); } } } return emptyIfNull(result); } /** Add all elements matching {@code predicate} in {@code source} to {@code dest}. */ public static void addIf(@Nullable List source, @NonNull Collection dest, @Nullable Predicate predicate) { for (int i = 0; i < size(source); i++) { final T item = source.get(i); if (predicate.test(item)) { dest.add(item); } } } /** * Returns a list of items resulting from applying the given function to each element of the * provided list. * * The resulting list will have the same {@link #size} as the input one. * * This is similar to {@link Stream#map} but without the overhead of creating an intermediate * {@link Stream} instance */ public static @NonNull List map(@Nullable List cur, Function f) { if (isEmpty(cur)) return Collections.emptyList(); final ArrayList result = new ArrayList<>(); for (int i = 0; i < cur.size(); i++) { result.add(f.apply(cur.get(i))); } return result; } /** * @see #map(List, Function) */ public static @NonNull Set map(@Nullable Set cur, Function f) { if (isEmpty(cur)) return Collections.emptySet(); ArraySet result = new ArraySet<>(); if (cur instanceof ArraySet) { ArraySet arraySet = (ArraySet) cur; int size = arraySet.size(); for (int i = 0; i < size; i++) { result.add(f.apply(arraySet.valueAt(i))); } } else { for (I item : cur) { result.add(f.apply(item)); } } return result; } /** * {@link #map(List, Function)} + {@link #filter(List, java.util.function.Predicate)} * * Calling this is equivalent (but more memory efficient) to: * * {@code * filter( * map(cur, f), * i -> { i != null }) * } */ public static @NonNull List mapNotNull(@Nullable List cur, Function f) { if (isEmpty(cur)) return Collections.emptyList(); List result = null; for (int i = 0; i < cur.size(); i++) { O transformed = f.apply(cur.get(i)); if (transformed != null) { result = add(result, transformed); } } return emptyIfNull(result); } /** * Returns the given list, or an immutable empty list if the provided list is null * * This can be used to guarantee null-safety without paying the price of extra allocations * * @see Collections#emptyList */ public static @NonNull List emptyIfNull(@Nullable List cur) { return cur == null ? Collections.emptyList() : cur; } /** * Returns the given set, or an immutable empty set if the provided set is null * * This can be used to guarantee null-safety without paying the price of extra allocations * * @see Collections#emptySet */ public static @NonNull Set emptyIfNull(@Nullable Set cur) { return cur == null ? Collections.emptySet() : cur; } /** * Returns the size of the given collection, or 0 if null */ public static int size(@Nullable Collection cur) { return cur != null ? cur.size() : 0; } /** * Returns whether the given collection {@link Collection#isEmpty is empty} or {@code null} */ public static boolean isEmpty(@Nullable Collection cur) { return size(cur) == 0; } /** * Returns the elements of the given list that are of type {@code c} */ public static @NonNull List filter(@Nullable List list, Class c) { if (isEmpty(list)) return Collections.emptyList(); ArrayList result = null; for (int i = 0; i < list.size(); i++) { final Object item = list.get(i); if (c.isInstance(item)) { result = ArrayUtils.add(result, (T) item); } } return emptyIfNull(result); } /** * Returns whether there exists at least one element in the list for which * condition {@code predicate} is true */ public static boolean any(@Nullable List items, java.util.function.Predicate predicate) { return find(items, predicate) != null; } /** * Returns the first element from the list for which * condition {@code predicate} is true, or null if there is no such element */ public static @Nullable T find(@Nullable List items, java.util.function.Predicate predicate) { if (isEmpty(items)) return null; for (int i = 0; i < items.size(); i++) { final T item = items.get(i); if (predicate.test(item)) return item; } return null; } /** * Similar to {@link List#add}, but with support for list values of {@code null} and * {@link Collections#emptyList} */ public static @NonNull List add(@Nullable List cur, T val) { if (cur == null || cur == Collections.emptyList()) { cur = new ArrayList<>(); } cur.add(val); return cur; } /** * @see #add(List, Object) */ public static @NonNull Set add(@Nullable Set cur, T val) { if (cur == null || cur == Collections.emptySet()) { cur = new ArraySet<>(); } cur.add(val); return cur; } /** * Similar to {@link List#remove}, but with support for list values of {@code null} and * {@link Collections#emptyList} */ public static @NonNull List remove(@Nullable List cur, T val) { if (isEmpty(cur)) { return emptyIfNull(cur); } cur.remove(val); return cur; } /** * @see #remove(List, Object) */ public static @NonNull Set remove(@Nullable Set cur, T val) { if (isEmpty(cur)) { return emptyIfNull(cur); } cur.remove(val); return cur; } /** * @return a list that will not be affected by mutations to the given original list. */ public static @NonNull List copyOf(@Nullable List cur) { return isEmpty(cur) ? Collections.emptyList() : new ArrayList<>(cur); } /** * @return a list that will not be affected by mutations to the given original list. */ public static @NonNull Set copyOf(@Nullable Set cur) { return isEmpty(cur) ? Collections.emptySet() : new ArraySet<>(cur); } /** * Applies {@code action} to each element in {@code cur} * * This avoids creating an iterator if the given set is an {@link ArraySet} */ public static void forEach(@Nullable Set cur, @Nullable ThrowingConsumer action) { if (cur == null || action == null) return; int size = cur.size(); if (size == 0) return; try { if (cur instanceof ArraySet) { ArraySet arraySet = (ArraySet) cur; for (int i = 0; i < size; i++) { action.acceptOrThrow(arraySet.valueAt(i)); } } else { for (T t : cur) { action.acceptOrThrow(t); } } } catch (Exception e) { throw ExceptionUtils.propagate(e); } } }