Home | History | Annotate | Download | only in guice
      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 }
    182