jdesprez | 7509fca | 2018-04-02 16:36:56 -0700 | [diff] [blame^] | 1 | /* |
| 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 | */ |
| 16 | package com.android.tradefed.guice; |
| 17 | |
| 18 | import static com.google.common.base.Preconditions.checkState; |
| 19 | |
| 20 | import com.android.tradefed.config.IConfiguration; |
| 21 | |
| 22 | import com.google.common.collect.Maps; |
| 23 | import com.google.inject.Guice; |
| 24 | import com.google.inject.Injector; |
| 25 | import com.google.inject.Key; |
| 26 | import com.google.inject.OutOfScopeException; |
| 27 | import com.google.inject.Provider; |
| 28 | import com.google.inject.Scope; |
| 29 | import com.google.inject.Scopes; |
| 30 | |
| 31 | import 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 | */ |
| 49 | public 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 | } |