blob: 5b11851baaeb0a54b47deb418bba00cde831b56f [file] [log] [blame]
jdesprez7509fca2018-04-02 16:36:56 -07001/*
2 * Copyright (C) 2018 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 */
16package com.android.tradefed.guice;
17
18import static com.google.common.base.Preconditions.checkState;
19
20import com.android.tradefed.config.IConfiguration;
21
22import com.google.common.collect.Maps;
23import com.google.inject.Guice;
24import com.google.inject.Injector;
25import com.google.inject.Key;
26import com.google.inject.OutOfScopeException;
27import com.google.inject.Provider;
28import com.google.inject.Scope;
29import com.google.inject.Scopes;
30
31import java.util.Map;
32
33/**
34 * Scopes a single Tradefed invocation.
35 *
36 * <p>The scope can be initialized with one or more seed values by calling <code>seed(key, value)
37 * </code> before the injector will be called upon to provide for this key. A typical use is for a
38 * test invocation to enter/exit the scope, representing an invocation Scope, and seed configuration
39 * objects. For each key inserted with seed(), you must include a corresponding binding:
40 *
41 * <pre><code>
42 * bind(key)
43 * .toProvider(SimpleScope.<key.class>seededKeyProvider())
44 * .in(InvocationScoped.class);
45 * </code></pre>
46 *
47 * FIXME: Possibly handle multi objects (like lists).
48 */
49public class InvocationScope implements Scope {
50
51 public InvocationScope() {}
52
53 private static final Provider<Object> SEEDED_KEY_PROVIDER =
54 new Provider<Object>() {
55 @Override
56 public Object get() {
57 throw new IllegalStateException(
58 "If you got here then it means that"
59 + " your code asked for scoped object which should have been"
60 + " explicitly seeded in this scope by calling"
61 + " SimpleScope.seed(), but was not.");
62 }
63 };
64
65 private static InvocationScope sDefaultInstance = null;
66
67 public static InvocationScope getDefault() {
68 if (sDefaultInstance == null) {
69 sDefaultInstance = new InvocationScope();
70 }
71 return sDefaultInstance;
72 }
73
74 private final ThreadLocal<Map<Key<?>, Object>> values = new ThreadLocal<Map<Key<?>, Object>>();
75
76 /** Start marking the scope of the Tradefed Invocation. */
77 public void enter() {
78 checkState(values.get() == null, "A scoping block is already in progress");
79 values.set(Maps.<Key<?>, Object>newHashMap());
80 }
81
82 /** Mark the end of the scope for the Tradefed Invocation. */
83 public void exit() {
84 checkState(values.get() != null, "No scoping block in progress");
85 values.remove();
86 }
87
88 /**
89 * Interface init between Tradefed and Guice: This is the place where TF object are seeded to
90 * the invocation scope to be used.
91 *
92 * @param config The Tradefed configuration.
93 */
94 public void seedConfiguration(IConfiguration config) {
95 // First seed the configuration itself
96 seed(IConfiguration.class, config);
97 // Then inject the seeded objects to the configuration.
98 injectToConfig(config);
99 }
100
101 private void injectToConfig(IConfiguration config) {
102 Injector injector = Guice.createInjector(new InvocationScopeModule(this));
103
104 // TODO: inject to TF objects that could require it.
105 // Do injection against current test objects: This allows to pass the injector
106 for (Object obj : config.getTests()) {
107 injector.injectMembers(obj);
108 }
109 }
110
111 /**
112 * Seed a key/value that will be available during the TF invocation scope to be used.
113 *
114 * @param key the key used to represent the object.
115 * @param value The actual object that will be available during the invocation.
116 */
117 public <T> void seed(Key<T> key, T value) {
118 Map<Key<?>, Object> scopedObjects = getScopedObjectMap(key);
119 checkState(
120 !scopedObjects.containsKey(key),
121 "A value for the key %s was "
122 + "already seeded in this scope. Old value: %s New value: %s",
123 key,
124 scopedObjects.get(key),
125 value);
126 scopedObjects.put(key, value);
127 }
128
129 /**
130 * Seed a key/value that will be available during the TF invocation scope to be used.
131 *
132 * @param clazz the Class used to represent the object.
133 * @param value The actual object that will be available during the invocation.
134 */
135 public <T> void seed(Class<T> clazz, T value) {
136 seed(Key.get(clazz), value);
137 }
138
139 @Override
140 public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
141 return new Provider<T>() {
142 @Override
143 public T get() {
144 Map<Key<?>, Object> scopedObjects = getScopedObjectMap(key);
145
146 @SuppressWarnings("unchecked")
147 T current = (T) scopedObjects.get(key);
148 if (current == null && !scopedObjects.containsKey(key)) {
149 current = unscoped.get();
150
151 // don't remember proxies; these exist only to serve circular dependencies
152 if (Scopes.isCircularProxy(current)) {
153 return current;
154 }
155
156 scopedObjects.put(key, current);
157 }
158 return current;
159 }
160 };
161 }
162
163 private <T> Map<Key<?>, Object> getScopedObjectMap(Key<T> key) {
164 Map<Key<?>, Object> scopedObjects = values.get();
165 if (scopedObjects == null) {
166 throw new OutOfScopeException("Cannot access " + key + " outside of a scoping block");
167 }
168 return scopedObjects;
169 }
170
171 /**
172 * Returns a provider that always throws exception complaining that the object in question must
173 * be seeded before it can be injected.
174 *
175 * @return typed provider
176 */
177 @SuppressWarnings({"unchecked"})
178 public static <T> Provider<T> seededKeyProvider() {
179 return (Provider<T>) SEEDED_KEY_PROVIDER;
180 }
181}