131 lines
4.5 KiB
Java
131 lines
4.5 KiB
Java
// Copyright 2018 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
package org.chromium.base;
|
|
|
|
import android.os.Process;
|
|
|
|
import java.util.HashMap;
|
|
|
|
/**
|
|
* A class that implements type-safe heterogeneous container. It can associate an object of type T
|
|
* with a type token (T.class) as a key. Mismatch of the type between them can be checked at compile
|
|
* time, hence type-safe. Objects are held using strong reference in the container. {@code null} is
|
|
* not allowed for key or object.
|
|
*
|
|
* <p>Can be used for an object that needs to have other objects attached to it without having to
|
|
* manage explicit references to them. Attached objects need to implement {@link UserData} so that
|
|
* they can be destroyed by {@link #destroy()}.
|
|
*
|
|
* <p>No operation takes effect once {@link #destroy()} is called.
|
|
*
|
|
* <p>Usage: <code>
|
|
* public class Foo {
|
|
* // Defines the container.
|
|
* private final UserDataHost mUserDataHost = new UserDataHost();
|
|
*
|
|
* public UserDataHost getUserDataHost() {
|
|
* return mUserDataHost;
|
|
* }
|
|
* }
|
|
*
|
|
* public class FooBar implements UserData {
|
|
*
|
|
* public FooBar from(UserDataHost host) {
|
|
* FooBar foobar = host.getUserData(FooBar.class);
|
|
* // Instantiate FooBar upon the first access.
|
|
* return foobar != null ? foobar : host.setUserData(FooBar.class, new FooBar());
|
|
* }
|
|
* }
|
|
*
|
|
* Foo foo = new Foo();
|
|
* ...
|
|
*
|
|
* FooBar bar = FooBar.from(foo.getUserDataHost());
|
|
*
|
|
* ...
|
|
*
|
|
* </code>
|
|
*/
|
|
public final class UserDataHost {
|
|
private final long mThreadId = Process.myTid();
|
|
|
|
private HashMap<Class<? extends UserData>, UserData> mUserDataMap = new HashMap<>();
|
|
|
|
private static void checkArgument(boolean condition) {
|
|
if (!condition) {
|
|
throw new IllegalArgumentException(
|
|
"Neither key nor object of UserDataHost can be null.");
|
|
}
|
|
}
|
|
|
|
private void checkThreadAndState() {
|
|
if (mThreadId != Process.myTid()) {
|
|
throw new IllegalStateException("UserData must only be used on a single thread.");
|
|
}
|
|
if (mUserDataMap == null) {
|
|
throw new IllegalStateException("Operation is not allowed after destroy().");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Associates the specified object with the specified key.
|
|
* @param key Type token with which the specified object is to be associated.
|
|
* @param object Object to be associated with the specified key.
|
|
* @return the object just stored, or {@code null} if storing the object failed.
|
|
*/
|
|
public <T extends UserData> T setUserData(Class<T> key, T object) {
|
|
checkThreadAndState();
|
|
checkArgument(key != null && object != null);
|
|
|
|
mUserDataMap.put(key, object);
|
|
return getUserData(key);
|
|
}
|
|
|
|
/**
|
|
* Returns the value to which the specified key is mapped, or null if this map
|
|
* contains no mapping for the key.
|
|
* @param key Type token for which the specified object is to be returned.
|
|
* @return the value to which the specified key is mapped, or null if this map
|
|
* contains no mapping for {@code key}.
|
|
*/
|
|
public <T extends UserData> T getUserData(Class<T> key) {
|
|
checkThreadAndState();
|
|
checkArgument(key != null);
|
|
|
|
return key.cast(mUserDataMap.get(key));
|
|
}
|
|
|
|
/**
|
|
* Removes the mapping for a key from this map. Exception will be thrown if
|
|
* the given key has no mapping.
|
|
* @param key Type token for which the specified object is to be removed.
|
|
* @return The previous value associated with {@code key}.
|
|
*/
|
|
public <T extends UserData> T removeUserData(Class<T> key) {
|
|
checkThreadAndState();
|
|
checkArgument(key != null);
|
|
|
|
if (!mUserDataMap.containsKey(key)) {
|
|
throw new IllegalStateException("UserData for the key is not present.");
|
|
}
|
|
return key.cast(mUserDataMap.remove(key));
|
|
}
|
|
|
|
/**
|
|
* Destroy all the managed {@link UserData} instances. This should be invoked at
|
|
* the end of the lifetime of the host that user data instances hang on to.
|
|
* The host stops managing them after this method is called.
|
|
*/
|
|
public void destroy() {
|
|
checkThreadAndState();
|
|
|
|
// Nulls out |mUserDataMap| first in order to prevent concurrent modification that
|
|
// might happen in the for loop below.
|
|
HashMap<Class<? extends UserData>, UserData> map = mUserDataMap;
|
|
mUserDataMap = null;
|
|
for (UserData userData : map.values()) userData.destroy();
|
|
}
|
|
}
|