1 // Copyright 2018 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.os.Process; 8 9 import java.util.HashMap; 10 11 /** 12 * A class that implements type-safe heterogeneous container. It can associate an object of type T 13 * with a type token (T.class) as a key. Mismatch of the type between them can be checked at compile 14 * time, hence type-safe. Objects are held using strong reference in the container. {@code null} is 15 * not allowed for key or object. 16 * 17 * <p>Can be used for an object that needs to have other objects attached to it without having to 18 * manage explicit references to them. Attached objects need to implement {@link UserData} so that 19 * they can be destroyed by {@link #destroy()}. 20 * 21 * <p>No operation takes effect once {@link #destroy()} is called. 22 * 23 * <p>Usage: <code> 24 * public class Foo { 25 * // Defines the container. 26 * private final UserDataHost mUserDataHost = new UserDataHost(); 27 * 28 * public UserDataHost getUserDataHost() { 29 * return mUserDataHost; 30 * } 31 * } 32 * 33 * public class FooBar implements UserData { 34 * 35 * public FooBar from(UserDataHost host) { 36 * FooBar foobar = host.getUserData(FooBar.class); 37 * // Instantiate FooBar upon the first access. 38 * return foobar != null ? foobar : host.setUserData(FooBar.class, new FooBar()); 39 * } 40 * } 41 * 42 * Foo foo = new Foo(); 43 * ... 44 * 45 * FooBar bar = FooBar.from(foo.getUserDataHost()); 46 * 47 * ... 48 * 49 * </code> 50 */ 51 public final class UserDataHost { 52 private final long mThreadId = Process.myTid(); 53 54 private HashMap<Class<? extends UserData>, UserData> mUserDataMap = new HashMap<>(); 55 checkArgument(boolean condition)56 private static void checkArgument(boolean condition) { 57 if (!condition) { 58 throw new IllegalArgumentException( 59 "Neither key nor object of UserDataHost can be null."); 60 } 61 } 62 checkThreadAndState()63 private void checkThreadAndState() { 64 if (mThreadId != Process.myTid()) { 65 throw new IllegalStateException("UserData must only be used on a single thread."); 66 } 67 if (mUserDataMap == null) { 68 throw new IllegalStateException("Operation is not allowed after destroy()."); 69 } 70 } 71 72 /** 73 * Associates the specified object with the specified key. 74 * @param key Type token with which the specified object is to be associated. 75 * @param object Object to be associated with the specified key. 76 * @return the object just stored, or {@code null} if storing the object failed. 77 */ setUserData(Class<T> key, T object)78 public <T extends UserData> T setUserData(Class<T> key, T object) { 79 checkThreadAndState(); 80 checkArgument(key != null && object != null); 81 82 mUserDataMap.put(key, object); 83 return getUserData(key); 84 } 85 86 /** 87 * Returns the value to which the specified key is mapped, or null if this map 88 * contains no mapping for the key. 89 * @param key Type token for which the specified object is to be returned. 90 * @return the value to which the specified key is mapped, or null if this map 91 * contains no mapping for {@code key}. 92 */ getUserData(Class<T> key)93 public <T extends UserData> T getUserData(Class<T> key) { 94 checkThreadAndState(); 95 checkArgument(key != null); 96 97 return key.cast(mUserDataMap.get(key)); 98 } 99 100 /** 101 * Removes the mapping for a key from this map. Exception will be thrown if 102 * the given key has no mapping. 103 * @param key Type token for which the specified object is to be removed. 104 * @return The previous value associated with {@code key}. 105 */ removeUserData(Class<T> key)106 public <T extends UserData> T removeUserData(Class<T> key) { 107 checkThreadAndState(); 108 checkArgument(key != null); 109 110 if (!mUserDataMap.containsKey(key)) { 111 throw new IllegalStateException("UserData for the key is not present."); 112 } 113 return key.cast(mUserDataMap.remove(key)); 114 } 115 116 /** 117 * Destroy all the managed {@link UserData} instances. This should be invoked at 118 * the end of the lifetime of the host that user data instances hang on to. 119 * The host stops managing them after this method is called. 120 */ destroy()121 public void destroy() { 122 checkThreadAndState(); 123 124 // Nulls out |mUserDataMap| first in order to prevent concurrent modification that 125 // might happen in the for loop below. 126 HashMap<Class<? extends UserData>, UserData> map = mUserDataMap; 127 mUserDataMap = null; 128 for (UserData userData : map.values()) userData.destroy(); 129 } 130 } 131