blob: a3d6ee52db6a5124a72a23e5f5d0ef1aaf3279df [file] [log] [blame]
/*
* Copyright (C) 2019 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.server.utils.quota;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.SparseArrayMap;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* A SparseArrayMap of ArrayMaps, which is suitable for holding userId-packageName-tag combination
* (UPTC)->object associations. Tags are any desired String.
*
* @see Uptc
*/
class UptcMap<T> {
private final SparseArrayMap<ArrayMap<String, T>> mData = new SparseArrayMap<>();
public void add(int userId, @NonNull String packageName, @Nullable String tag,
@Nullable T obj) {
ArrayMap<String, T> data = mData.get(userId, packageName);
if (data == null) {
data = new ArrayMap<>();
mData.add(userId, packageName, data);
}
data.put(tag, obj);
}
public void clear() {
mData.clear();
}
public boolean contains(int userId, @NonNull String packageName) {
return mData.contains(userId, packageName);
}
public boolean contains(int userId, @NonNull String packageName, @Nullable String tag) {
// This structure never inserts a null ArrayMap, so if get(userId, packageName) returns
// null, the UPTC was never inserted.
ArrayMap<String, T> data = mData.get(userId, packageName);
return data != null && data.containsKey(tag);
}
/** Removes all the data for the user, if there was any. */
public void delete(int userId) {
mData.delete(userId);
}
/** Removes the data for the user, package, and tag, if there was any. */
public void delete(int userId, @NonNull String packageName, @Nullable String tag) {
final ArrayMap<String, T> data = mData.get(userId, packageName);
if (data != null) {
data.remove(tag);
if (data.size() == 0) {
mData.delete(userId, packageName);
}
}
}
/** Removes the data for the user and package, if there was any. */
public ArrayMap<String, T> delete(int userId, @NonNull String packageName) {
return mData.delete(userId, packageName);
}
/**
* Returns the set of tag -> object mappings for the given userId and packageName
* combination.
*/
@Nullable
public ArrayMap<String, T> get(int userId, @NonNull String packageName) {
return mData.get(userId, packageName);
}
/** Returns the saved object for the given UPTC. */
@Nullable
public T get(int userId, @NonNull String packageName, @Nullable String tag) {
final ArrayMap<String, T> data = mData.get(userId, packageName);
return data != null ? data.get(tag) : null;
}
/**
* Returns the saved object for the given UPTC. If there was no saved object, it will create a
* new object using creator, insert it, and return it.
*/
@Nullable
public T getOrCreate(int userId, @NonNull String packageName, @Nullable String tag,
Function<Void, T> creator) {
final ArrayMap<String, T> data = mData.get(userId, packageName);
if (data == null || !data.containsKey(tag)) {
// We've never inserted data for this combination before. Create a new object.
final T val = creator.apply(null);
add(userId, packageName, tag, val);
return val;
}
return data.get(tag);
}
/** Returns the userId at the given index. */
private int getUserIdAtIndex(int index) {
return mData.keyAt(index);
}
/** Returns the package name at the given index. */
@NonNull
private String getPackageNameAtIndex(int userIndex, int packageIndex) {
return mData.keyAt(userIndex, packageIndex);
}
/** Returns the tag at the given index. */
@NonNull
private String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
// This structure never inserts a null ArrayMap, so if the indices are valid, valueAt()
// won't return null.
return mData.valueAt(userIndex, packageIndex).keyAt(tagIndex);
}
/** Returns the size of the outer (userId) array. */
public int userCount() {
return mData.numMaps();
}
/** Returns the number of packages saved for a given userId. */
public int packageCountForUser(int userId) {
return mData.numElementsForKey(userId);
}
/** Returns the number of tags saved for a given userId-packageName combination. */
public int tagCountForUserAndPackage(int userId, @NonNull String packageName) {
final ArrayMap data = mData.get(userId, packageName);
return data != null ? data.size() : 0;
}
/** Returns the value T at the given user, package, and tag indices. */
@Nullable
public T valueAt(int userIndex, int packageIndex, int tagIndex) {
final ArrayMap<String, T> data = mData.valueAt(userIndex, packageIndex);
return data != null ? data.valueAt(tagIndex) : null;
}
public void forEach(Consumer<T> consumer) {
mData.forEach((tagMap) -> {
for (int i = tagMap.size() - 1; i >= 0; --i) {
consumer.accept(tagMap.valueAt(i));
}
});
}
public void forEach(UptcDataConsumer<T> consumer) {
final int uCount = userCount();
for (int u = 0; u < uCount; ++u) {
final int userId = getUserIdAtIndex(u);
final int pkgCount = packageCountForUser(userId);
for (int p = 0; p < pkgCount; ++p) {
final String pkgName = getPackageNameAtIndex(u, p);
final int tagCount = tagCountForUserAndPackage(userId, pkgName);
for (int t = 0; t < tagCount; ++t) {
final String tag = getTagAtIndex(u, p, t);
consumer.accept(userId, pkgName, tag, get(userId, pkgName, tag));
}
}
}
}
interface UptcDataConsumer<D> {
void accept(int userId, @NonNull String packageName, @Nullable String tag, @Nullable D obj);
}
}