• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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