| // 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(); |
| } |
| } |