• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.nearby.common.locator;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.ContextWrapper;
22 import android.util.Log;
23 
24 import androidx.annotation.VisibleForTesting;
25 
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.Map;
29 
30 /** Collection of bindings that map service types to their respective implementation(s). */
31 public class Locator {
32     private static final Object UNBOUND = new Object();
33     private final Context mContext;
34     @Nullable
35     private Locator mParent;
36     private final String mTag; // For debugging
37     private final Map<Class<?>, Object> mBindings = new HashMap<>();
38     private final ArrayList<Module> mModules = new ArrayList<>();
39 
40     /** Thrown upon attempt to bind an interface twice. */
41     public static class DuplicateBindingException extends RuntimeException {
DuplicateBindingException(String msg)42         DuplicateBindingException(String msg) {
43             super(msg);
44         }
45     }
46 
47     /** Constructor with a null parent. */
Locator(Context context)48     public Locator(Context context) {
49         this(context, null);
50     }
51 
52     /**
53      * Constructor. Supply a valid context and the Locator's parent.
54      *
55      * <p>To find a suitable parent you may want to use findLocator.
56      */
Locator(Context context, @Nullable Locator parent)57     public Locator(Context context, @Nullable Locator parent) {
58         this.mContext = context;
59         this.mParent = parent;
60         this.mTag = context.getClass().getName();
61     }
62 
63     /** Attaches the parent to the locator. */
attachParent(Locator parent)64     public void attachParent(Locator parent) {
65         this.mParent = parent;
66     }
67 
68     /** Associates the specified type with the supplied instance. */
bind(Class<T> type, T instance)69     public <T extends Object> Locator bind(Class<T> type, T instance) {
70         bindKeyValue(type, instance);
71         return this;
72     }
73 
74     /** For tests only. Disassociates the specified type from any instance. */
75     @VisibleForTesting
overrideBindingForTest(Class<T> type, T instance)76     public <T extends Object> Locator overrideBindingForTest(Class<T> type, T instance) {
77         mBindings.remove(type);
78         return bind(type, instance);
79     }
80 
81     /** For tests only. Force Locator to return null when try to get an instance. */
82     @VisibleForTesting
removeBindingForTest(Class<T> type)83     public <T> Locator removeBindingForTest(Class<T> type) {
84         Locator locator = this;
85         do {
86             locator.mBindings.put(type, UNBOUND);
87             locator = locator.mParent;
88         } while (locator != null);
89         return this;
90     }
91 
92     /** Binds a module. */
bind(Module module)93     public synchronized Locator bind(Module module) {
94         mModules.add(module);
95         return this;
96     }
97 
98     /**
99      * Searches the chain of locators for a binding for the given type.
100      *
101      * @throws IllegalStateException if no binding is found.
102      */
get(Class<T> type)103     public <T> T get(Class<T> type) {
104         T instance = getOptional(type);
105         if (instance != null) {
106             return instance;
107         }
108 
109         String errorMessage = getUnboundErrorMessage(type);
110         throw new IllegalStateException(errorMessage);
111     }
112 
113     @VisibleForTesting
getUnboundErrorMessage(Class<?> type)114     String getUnboundErrorMessage(Class<?> type) {
115         StringBuilder sb = new StringBuilder();
116         sb.append("Unbound type: ").append(type.getName()).append("\n").append(
117                 "Searched locators:\n");
118         Locator locator = this;
119         while (true) {
120             sb.append(locator.mTag);
121             locator = locator.mParent;
122             if (locator == null) {
123                 break;
124             }
125             sb.append(" ->\n");
126         }
127         return sb.toString();
128     }
129 
130     /**
131      * Searches the chain of locators for a binding for the given type. Returns null if no locator
132      * was
133      * found.
134      */
135     @Nullable
getOptional(Class<T> type)136     public <T> T getOptional(Class<T> type) {
137         Locator locator = this;
138         do {
139             T instance = locator.getInstance(type);
140             if (instance != null) {
141                 return instance;
142             }
143             locator = locator.mParent;
144         } while (locator != null);
145         return null;
146     }
147 
bindKeyValue(Class<T> key, T value)148     private synchronized <T extends Object> void bindKeyValue(Class<T> key, T value) {
149         Object boundInstance = mBindings.get(key);
150         if (boundInstance != null) {
151             if (boundInstance == UNBOUND) {
152                 Log.w(mTag, "Bind call too late - someone already tried to get: " + key);
153             } else {
154                 throw new DuplicateBindingException("Duplicate binding: " + key);
155             }
156         }
157         mBindings.put(key, value);
158     }
159 
160     // Suppress warning of cast from Object -> T
161     @SuppressWarnings("unchecked")
162     @Nullable
getInstance(Class<T> type)163     private synchronized <T> T getInstance(Class<T> type) {
164         if (mContext == null) {
165             throw new IllegalStateException("Locator not initialized yet.");
166         }
167 
168         T instance = (T) mBindings.get(type);
169         if (instance != null) {
170             return instance != UNBOUND ? instance : null;
171         }
172 
173         // Ask modules to supply a binding
174         int moduleCount = mModules.size();
175         for (int i = 0; i < moduleCount; i++) {
176             mModules.get(i).configure(mContext, type, this);
177         }
178 
179         instance = (T) mBindings.get(type);
180         if (instance == null) {
181             mBindings.put(type, UNBOUND);
182         }
183         return instance;
184     }
185 
186     /**
187      * Iterates over all bound objects and gives the modules a chance to clean up the objects they
188      * have created.
189      */
destroy()190     public synchronized void destroy() {
191         for (Class<?> type : mBindings.keySet()) {
192             Object instance = mBindings.get(type);
193             if (instance == UNBOUND) {
194                 continue;
195             }
196 
197             for (Module module : mModules) {
198                 module.destroy(mContext, type, instance);
199             }
200         }
201         mBindings.clear();
202     }
203 
204     /** Returns true if there are no bindings. */
isEmpty()205     public boolean isEmpty() {
206         return mBindings.isEmpty();
207     }
208 
209     /** Returns the parent locator or null if no parent. */
210     @Nullable
getParent()211     public Locator getParent() {
212         return mParent;
213     }
214 
215     /**
216      * Finds the first locator, then searches the chain of locators for a binding for the given
217      * type.
218      *
219      * @throws IllegalStateException if no binding is found.
220      */
get(Context context, Class<T> type)221     public static <T> T get(Context context, Class<T> type) {
222         Locator locator = findLocator(context);
223         if (locator == null) {
224             throw new IllegalStateException("No locator found in context " + context);
225         }
226         return locator.get(type);
227     }
228 
229     /**
230      * Find the first locator from the context wrapper.
231      */
getFromContextWrapper(LocatorContextWrapper wrapper, Class<T> type)232     public static <T> T getFromContextWrapper(LocatorContextWrapper wrapper, Class<T> type) {
233         Locator locator = wrapper.getLocator();
234         if (locator == null) {
235             throw new IllegalStateException("No locator found in context wrapper");
236         }
237         return locator.get(type);
238     }
239 
240     /**
241      * Finds the first locator, then searches the chain of locators for a binding for the given
242      * type.
243      * Returns null if no binding was found.
244      */
245     @Nullable
getOptional(Context context, Class<T> type)246     public static <T> T getOptional(Context context, Class<T> type) {
247         Locator locator = findLocator(context);
248         if (locator == null) {
249             return null;
250         }
251         return locator.getOptional(type);
252     }
253 
254     /** Finds the first locator in the context hierarchy. */
255     @Nullable
findLocator(Context context)256     public static Locator findLocator(Context context) {
257         Context applicationContext = context.getApplicationContext();
258         boolean applicationContextVisited = false;
259 
260         Context searchContext = context;
261         do {
262             Locator locator = tryGetLocator(searchContext);
263             if (locator != null) {
264                 return locator;
265             }
266 
267             applicationContextVisited |= (searchContext == applicationContext);
268 
269             if (searchContext instanceof ContextWrapper) {
270                 searchContext = ((ContextWrapper) context).getBaseContext();
271 
272                 if (searchContext == null) {
273                     throw new IllegalStateException(
274                             "Invalid ContextWrapper -- If this is a Robolectric test, "
275                                     + "have you called ActivityController.create()?");
276                 }
277             } else if (!applicationContextVisited) {
278                 searchContext = applicationContext;
279             } else {
280                 searchContext = null;
281             }
282         } while (searchContext != null);
283 
284         return null;
285     }
286 
287     @Nullable
tryGetLocator(Object object)288     private static Locator tryGetLocator(Object object) {
289         if (object instanceof LocatorContext) {
290             Locator locator = ((LocatorContext) object).getLocator();
291             if (locator == null) {
292                 throw new IllegalStateException(
293                         "LocatorContext must not return null Locator: " + object);
294             }
295             return locator;
296         }
297         return null;
298     }
299 }
300