// 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. * *

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()}. * *

No operation takes effect once {@link #destroy()} is called. * *

Usage: * 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()); * * ... * * */ public final class UserDataHost { private final long mThreadId = Process.myTid(); private HashMap, 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 setUserData(Class 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 getUserData(Class 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 removeUserData(Class 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, UserData> map = mUserDataMap; mUserDataMap = null; for (UserData userData : map.values()) userData.destroy(); } }