Home | History | Annotate | Download | only in internal
      1 /**
      2  * Copyright (C) 2011 Google Inc.
      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.google.inject.internal;
     18 
     19 import com.google.common.cache.CacheBuilder;
     20 import com.google.common.cache.CacheLoader;
     21 import com.google.common.cache.LoadingCache;
     22 import com.google.common.collect.ImmutableList;
     23 import com.google.common.collect.ImmutableSet;
     24 import com.google.common.collect.Lists;
     25 import com.google.inject.Binding;
     26 import com.google.inject.Injector;
     27 import com.google.inject.Key;
     28 import com.google.inject.Stage;
     29 import com.google.inject.spi.ProvisionListener;
     30 import com.google.inject.spi.ProvisionListenerBinding;
     31 
     32 import java.util.List;
     33 import java.util.Set;
     34 import java.util.logging.Logger;
     35 
     36 /**
     37  * {@link ProvisionListenerStackCallback} for each key.
     38  *
     39  * @author sameb (at) google.com (Sam Berlin)
     40  */
     41 final class ProvisionListenerCallbackStore {
     42 
     43   // TODO(sameb): Consider exposing this in the API somehow?  Maybe?
     44   // Lots of code often want to skip over the internal stuffs.
     45   private static final Set<Key<?>> INTERNAL_BINDINGS =
     46       ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(Logger.class));
     47 
     48   private final ImmutableList<ProvisionListenerBinding> listenerBindings;
     49 
     50   private final LoadingCache<KeyBinding, ProvisionListenerStackCallback<?>> cache
     51       = CacheBuilder.newBuilder().build(
     52           new CacheLoader<KeyBinding, ProvisionListenerStackCallback<?>>() {
     53             public ProvisionListenerStackCallback<?> load(KeyBinding key) {
     54               return create(key.binding);
     55             }
     56           });
     57 
     58   ProvisionListenerCallbackStore(List<ProvisionListenerBinding> listenerBindings) {
     59     this.listenerBindings = ImmutableList.copyOf(listenerBindings);
     60   }
     61 
     62   /** Returns a new {@link ProvisionListenerStackCallback} for the key.
     63    */
     64   @SuppressWarnings("unchecked") // the ProvisionListenerStackCallback type always agrees with the passed type
     65   public <T> ProvisionListenerStackCallback<T> get(Binding<T> binding) {
     66     // Never notify any listeners for internal bindings.
     67     if (!INTERNAL_BINDINGS.contains(binding.getKey())) {
     68       return (ProvisionListenerStackCallback<T>) cache.getUnchecked(
     69           new KeyBinding(binding.getKey(), binding));
     70     }
     71     return ProvisionListenerStackCallback.emptyListener();
     72   }
     73 
     74   /**
     75    * Purges a key from the cache. Use this only if the type is not actually valid for
     76    * binding and needs to be purged. (See issue 319 and
     77    * ImplicitBindingTest#testCircularJitBindingsLeaveNoResidue and
     78    * #testInstancesRequestingProvidersForThemselvesWithChildInjectors for examples of when this is
     79    * necessary.)
     80    *
     81    * Returns true if the type was stored in the cache, false otherwise.
     82    */
     83   boolean remove(Binding<?> type) {
     84     return cache.asMap().remove(type) != null;
     85   }
     86 
     87   /**
     88    * Creates a new {@link ProvisionListenerStackCallback} with the correct listeners
     89    * for the key.
     90    */
     91   private <T> ProvisionListenerStackCallback<T> create(Binding<T> binding) {
     92     List<ProvisionListener> listeners = null;
     93     for (ProvisionListenerBinding provisionBinding : listenerBindings) {
     94       if (provisionBinding.getBindingMatcher().matches(binding)) {
     95         if (listeners == null) {
     96           listeners = Lists.newArrayList();
     97         }
     98         listeners.addAll(provisionBinding.getListeners());
     99       }
    100     }
    101     if (listeners == null || listeners.isEmpty()) {
    102       // Optimization: don't bother constructing the callback if there are
    103       // no listeners.
    104       return ProvisionListenerStackCallback.emptyListener();
    105     }
    106     return new ProvisionListenerStackCallback<T>(binding, listeners);
    107   }
    108 
    109   /** A struct that holds key & binding but uses just key for equality/hashcode. */
    110   private static class KeyBinding {
    111     final Key<?> key;
    112     final Binding<?> binding;
    113 
    114     KeyBinding(Key<?> key, Binding<?> binding) {
    115       this.key = key;
    116       this.binding = binding;
    117     }
    118 
    119     @Override
    120     public boolean equals(Object obj) {
    121       return obj instanceof KeyBinding && key.equals(((KeyBinding)obj).key);
    122     }
    123     @Override
    124     public int hashCode() {
    125       return key.hashCode();
    126     }
    127   }
    128 }
    129