blob: a8eb046406aaced0629f150788c5e20afd227c79 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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 void checkThreadAndState() {
assert mThreadId == Process.myTid() : "UserData must only be used on a single thread.";
assert mUserDataMap != null : "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();
assert key != null && object != null : "Neither key nor object of UserDataHost can be 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();
assert key != null : "UserDataHost key cannot be null";
return key.cast(mUserDataMap.get(key));
}
/**
* Removes the mapping for a key from this map. Assertion 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();
assert key != null : "UserDataHost key cannot be null";
assert mUserDataMap.containsKey(key) : "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();
}
}