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 InvocationScope()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 getDefault()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. */ enter()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. */ exit()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 */ seedConfiguration(IConfiguration config)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 injectToConfig(IConfiguration config)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 */ seed(Key<T> key, T value)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 */ seed(Class<T> clazz, T value)135 public <T> void seed(Class<T> clazz, T value) { 136 seed(Key.get(clazz), value); 137 } 138 139 @Override scope(final Key<T> key, final Provider<T> unscoped)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 } 182