| /** |
| * Copyright (C) 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.inject.internal; |
| |
| import com.google.common.base.Objects; |
| import com.google.common.base.Preconditions; |
| import com.google.common.cache.Cache; |
| import com.google.common.cache.CacheBuilder; |
| import com.google.common.cache.RemovalCause; |
| import com.google.common.cache.RemovalListener; |
| import com.google.common.cache.RemovalNotification; |
| import com.google.common.collect.LinkedHashMultiset; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Multiset; |
| import com.google.common.collect.Sets; |
| import com.google.inject.Key; |
| import com.google.inject.internal.util.SourceProvider; |
| |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Minimal set that doesn't hold strong references to the contained keys. |
| * |
| * @author dweis@google.com (Daniel Weis) |
| */ |
| final class WeakKeySet { |
| |
| private Map<Key<?>, Multiset<Object>> backingMap; |
| |
| /** |
| * This is already locked externally on add and getSources but we need it to handle clean up in |
| * the evictionCache's RemovalListener. |
| */ |
| private final Object lock; |
| |
| /** |
| * Tracks child injector lifetimes and evicts blacklisted keys/sources after the child injector is |
| * garbage collected. |
| */ |
| private final Cache<State, Set<KeyAndSource>> evictionCache = CacheBuilder.newBuilder() |
| .weakKeys() |
| .removalListener( |
| new RemovalListener<State, Set<KeyAndSource>>() { |
| @Override |
| public void onRemoval(RemovalNotification<State, Set<KeyAndSource>> notification) { |
| Preconditions.checkState(RemovalCause.COLLECTED.equals(notification.getCause())); |
| |
| cleanUpForCollectedState(notification.getValue()); |
| } |
| }) |
| .build(); |
| |
| /** |
| * There may be multiple child injectors blacklisting a certain key so only remove the source |
| * that's relevant. |
| */ |
| private void cleanUpForCollectedState(Set<KeyAndSource> keysAndSources) { |
| synchronized (lock) { |
| for (KeyAndSource keyAndSource : keysAndSources) { |
| Multiset<Object> set = backingMap.get(keyAndSource.key); |
| if (set != null) { |
| set.remove(keyAndSource.source); |
| if (set.isEmpty()) { |
| backingMap.remove(keyAndSource.key); |
| } |
| } |
| } |
| } |
| } |
| |
| WeakKeySet(Object lock) { |
| this.lock = lock; |
| } |
| |
| public void add(Key<?> key, State state, Object source) { |
| if (backingMap == null) { |
| backingMap = Maps.newHashMap(); |
| } |
| // if it's an instanceof Class, it was a JIT binding, which we don't |
| // want to retain. |
| if (source instanceof Class || source == SourceProvider.UNKNOWN_SOURCE) { |
| source = null; |
| } |
| Multiset<Object> sources = backingMap.get(key); |
| if (sources == null) { |
| sources = LinkedHashMultiset.create(); |
| backingMap.put(key, sources); |
| } |
| Object convertedSource = Errors.convert(source); |
| sources.add(convertedSource); |
| |
| // Avoid all the extra work if we can. |
| if (state.parent() != State.NONE) { |
| Set<KeyAndSource> keyAndSources = evictionCache.getIfPresent(state); |
| if (keyAndSources == null) { |
| evictionCache.put(state, keyAndSources = Sets.newHashSet()); |
| } |
| keyAndSources.add(new KeyAndSource(key, convertedSource)); |
| } |
| } |
| |
| public boolean contains(Key<?> key) { |
| evictionCache.cleanUp(); |
| return backingMap != null && backingMap.containsKey(key); |
| } |
| |
| public Set<Object> getSources(Key<?> key) { |
| evictionCache.cleanUp(); |
| Multiset<Object> sources = (backingMap == null) ? null : backingMap.get(key); |
| return (sources == null) ? null : sources.elementSet(); |
| } |
| |
| private static final class KeyAndSource { |
| final Key<?> key; |
| final Object source; |
| |
| KeyAndSource(Key<?> key, Object source) { |
| this.key = key; |
| this.source = source; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(key, source); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| |
| if (!(obj instanceof KeyAndSource)) { |
| return false; |
| } |
| |
| KeyAndSource other = (KeyAndSource) obj; |
| return Objects.equal(key, other.key) |
| && Objects.equal(source, other.source); |
| } |
| } |
| } |