• 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 
getUnboundErrorMessage(Class<?> type)113     private String getUnboundErrorMessage(Class<?> type) {
114         StringBuilder sb = new StringBuilder();
115         sb.append("Unbound type: ").append(type.getName()).append("\n").append(
116                 "Searched locators:\n");
117         Locator locator = this;
118         while (true) {
119             sb.append(locator.mTag);
120             locator = locator.mParent;
121             if (locator == null) {
122                 break;
123             }
124             sb.append(" ->\n");
125         }
126         return sb.toString();
127     }
128 
129     /**
130      * Searches the chain of locators for a binding for the given type. Returns null if no locator
131      * was
132      * found.
133      */
134     @Nullable
getOptional(Class<T> type)135     public <T> T getOptional(Class<T> type) {
136         Locator locator = this;
137         do {
138             T instance = locator.getInstance(type);
139             if (instance != null) {
140                 return instance;
141             }
142             locator = locator.mParent;
143         } while (locator != null);
144         return null;
145     }
146 
bindKeyValue(Class<T> key, T value)147     private synchronized <T extends Object> void bindKeyValue(Class<T> key, T value) {
148         Object boundInstance = mBindings.get(key);
149         if (boundInstance != null) {
150             if (boundInstance == UNBOUND) {
151                 Log.w(mTag, "Bind call too late - someone already tried to get: " + key);
152             } else {
153                 throw new DuplicateBindingException("Duplicate binding: " + key);
154             }
155         }
156         mBindings.put(key, value);
157     }
158 
159     // Suppress warning of cast from Object -> T
160     @SuppressWarnings("unchecked")
161     @Nullable
getInstance(Class<T> type)162     private synchronized <T> T getInstance(Class<T> type) {
163         if (mContext == null) {
164             throw new IllegalStateException("Locator not initialized yet.");
165         }
166 
167         T instance = (T) mBindings.get(type);
168         if (instance != null) {
169             return instance != UNBOUND ? instance : null;
170         }
171 
172         // Ask modules to supply a binding
173         int moduleCount = mModules.size();
174         for (int i = 0; i < moduleCount; i++) {
175             mModules.get(i).configure(mContext, type, this);
176         }
177 
178         instance = (T) mBindings.get(type);
179         if (instance == null) {
180             mBindings.put(type, UNBOUND);
181         }
182         return instance;
183     }
184 
185     /**
186      * Iterates over all bound objects and gives the modules a chance to clean up the objects they
187      * have created.
188      */
destroy()189     public synchronized void destroy() {
190         for (Class<?> type : mBindings.keySet()) {
191             Object instance = mBindings.get(type);
192             if (instance == UNBOUND) {
193                 continue;
194             }
195 
196             for (Module module : mModules) {
197                 module.destroy(mContext, type, instance);
198             }
199         }
200         mBindings.clear();
201     }
202 
203     /** Returns true if there are no bindings. */
isEmpty()204     public boolean isEmpty() {
205         return mBindings.isEmpty();
206     }
207 
208     /** Returns the parent locator or null if no parent. */
209     @Nullable
getParent()210     public Locator getParent() {
211         return mParent;
212     }
213 
214     /**
215      * Finds the first locator, then searches the chain of locators for a binding for the given
216      * type.
217      *
218      * @throws IllegalStateException if no binding is found.
219      */
get(Context context, Class<T> type)220     public static <T> T get(Context context, Class<T> type) {
221         Locator locator = findLocator(context);
222         if (locator == null) {
223             throw new IllegalStateException("No locator found in context " + context);
224         }
225         return locator.get(type);
226     }
227 
228     /**
229      * Find the first locator from the context wrapper.
230      */
getFromContextWrapper(LocatorContextWrapper wrapper, Class<T> type)231     public static <T> T getFromContextWrapper(LocatorContextWrapper wrapper, Class<T> type) {
232         Locator locator = wrapper.getLocator();
233         if (locator == null) {
234             throw new IllegalStateException("No locator found in context wrapper");
235         }
236         return locator.get(type);
237     }
238 
239     /**
240      * Finds the first locator, then searches the chain of locators for a binding for the given
241      * type.
242      * Returns null if no binding was found.
243      */
244     @Nullable
getOptional(Context context, Class<T> type)245     public static <T> T getOptional(Context context, Class<T> type) {
246         Locator locator = findLocator(context);
247         if (locator == null) {
248             return null;
249         }
250         return locator.getOptional(type);
251     }
252 
253     /** Finds the first locator in the context hierarchy. */
254     @Nullable
findLocator(Context context)255     public static Locator findLocator(Context context) {
256         Context applicationContext = context.getApplicationContext();
257         boolean applicationContextVisited = false;
258 
259         Context searchContext = context;
260         do {
261             Locator locator = tryGetLocator(searchContext);
262             if (locator != null) {
263                 return locator;
264             }
265 
266             applicationContextVisited |= (searchContext == applicationContext);
267 
268             if (searchContext instanceof ContextWrapper) {
269                 searchContext = ((ContextWrapper) context).getBaseContext();
270 
271                 if (searchContext == null) {
272                     throw new IllegalStateException(
273                             "Invalid ContextWrapper -- If this is a Robolectric test, "
274                                     + "have you called ActivityController.create()?");
275                 }
276             } else if (!applicationContextVisited) {
277                 searchContext = applicationContext;
278             } else {
279                 searchContext = null;
280             }
281         } while (searchContext != null);
282 
283         return null;
284     }
285 
286     @Nullable
tryGetLocator(Object object)287     private static Locator tryGetLocator(Object object) {
288         if (object instanceof LocatorContext) {
289             Locator locator = ((LocatorContext) object).getLocator();
290             if (locator == null) {
291                 throw new IllegalStateException(
292                         "LocatorContext must not return null Locator: " + object);
293             }
294             return locator;
295         }
296         return null;
297     }
298 }
299