Home | History | Annotate | Download | only in internal
      1 /**
      2  * Copyright (C) 2008 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.base.Objects;
     20 import com.google.common.base.Preconditions;
     21 import com.google.common.cache.Cache;
     22 import com.google.common.cache.CacheBuilder;
     23 import com.google.common.cache.RemovalCause;
     24 import com.google.common.cache.RemovalListener;
     25 import com.google.common.cache.RemovalNotification;
     26 import com.google.common.collect.LinkedHashMultiset;
     27 import com.google.common.collect.Maps;
     28 import com.google.common.collect.Multiset;
     29 import com.google.common.collect.Sets;
     30 import com.google.inject.Key;
     31 import com.google.inject.internal.util.SourceProvider;
     32 
     33 import java.util.Map;
     34 import java.util.Set;
     35 
     36 /**
     37  * Minimal set that doesn't hold strong references to the contained keys.
     38  *
     39  * @author dweis (at) google.com (Daniel Weis)
     40  */
     41 final class WeakKeySet {
     42 
     43   private Map<Key<?>, Multiset<Object>> backingMap;
     44 
     45   /**
     46    * This is already locked externally on add and getSources but we need it to handle clean up in
     47    * the evictionCache's RemovalListener.
     48    */
     49   private final Object lock;
     50 
     51   /**
     52    * Tracks child injector lifetimes and evicts blacklisted keys/sources after the child injector is
     53    * garbage collected.
     54    */
     55   private final Cache<State, Set<KeyAndSource>> evictionCache = CacheBuilder.newBuilder()
     56       .weakKeys()
     57       .removalListener(
     58           new RemovalListener<State, Set<KeyAndSource>>() {
     59             @Override
     60             public void onRemoval(RemovalNotification<State, Set<KeyAndSource>> notification) {
     61               Preconditions.checkState(RemovalCause.COLLECTED.equals(notification.getCause()));
     62 
     63               cleanUpForCollectedState(notification.getValue());
     64             }
     65           })
     66       .build();
     67 
     68   /**
     69    * There may be multiple child injectors blacklisting a certain key so only remove the source
     70    * that's relevant.
     71    */
     72   private void cleanUpForCollectedState(Set<KeyAndSource> keysAndSources) {
     73     synchronized (lock) {
     74       for (KeyAndSource keyAndSource : keysAndSources) {
     75         Multiset<Object> set = backingMap.get(keyAndSource.key);
     76         if (set != null) {
     77           set.remove(keyAndSource.source);
     78           if (set.isEmpty()) {
     79             backingMap.remove(keyAndSource.key);
     80           }
     81         }
     82       }
     83     }
     84   }
     85 
     86   WeakKeySet(Object lock) {
     87     this.lock = lock;
     88   }
     89 
     90   public void add(Key<?> key, State state, Object source) {
     91     if (backingMap == null) {
     92       backingMap = Maps.newHashMap();
     93     }
     94     // if it's an instanceof Class, it was a JIT binding, which we don't
     95     // want to retain.
     96     if (source instanceof Class || source == SourceProvider.UNKNOWN_SOURCE) {
     97       source = null;
     98     }
     99     Multiset<Object> sources = backingMap.get(key);
    100     if (sources == null) {
    101       sources = LinkedHashMultiset.create();
    102       backingMap.put(key, sources);
    103     }
    104     Object convertedSource = Errors.convert(source);
    105     sources.add(convertedSource);
    106 
    107     // Avoid all the extra work if we can.
    108     if (state.parent() != State.NONE) {
    109       Set<KeyAndSource> keyAndSources = evictionCache.getIfPresent(state);
    110       if (keyAndSources == null) {
    111         evictionCache.put(state, keyAndSources = Sets.newHashSet());
    112       }
    113       keyAndSources.add(new KeyAndSource(key, convertedSource));
    114     }
    115   }
    116 
    117   public boolean contains(Key<?> key) {
    118     evictionCache.cleanUp();
    119     return backingMap != null && backingMap.containsKey(key);
    120   }
    121 
    122   public Set<Object> getSources(Key<?> key) {
    123     evictionCache.cleanUp();
    124     Multiset<Object> sources = (backingMap == null) ? null : backingMap.get(key);
    125     return (sources == null) ? null : sources.elementSet();
    126   }
    127 
    128   private static final class KeyAndSource {
    129     final Key<?> key;
    130     final Object source;
    131 
    132     KeyAndSource(Key<?> key, Object source) {
    133       this.key = key;
    134       this.source = source;
    135     }
    136 
    137     @Override
    138     public int hashCode() {
    139       return Objects.hashCode(key, source);
    140     }
    141 
    142     @Override
    143     public boolean equals(Object obj) {
    144       if (this == obj) {
    145         return true;
    146       }
    147 
    148       if (!(obj instanceof KeyAndSource)) {
    149         return false;
    150       }
    151 
    152       KeyAndSource other = (KeyAndSource) obj;
    153       return Objects.equal(key, other.key)
    154           && Objects.equal(source, other.source);
    155     }
    156   }
    157 }
    158