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.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;
final class WeakKeySet {
private Map<Key<?>, Multiset<Object>> backingMap;
private final Object lock;
private final Cache<State, Set<KeyAndSource>> evictionCache =
CacheBuilder.newBuilder().weakKeys().removalListener(this::cleanupOnRemoval).build();
private void cleanupOnRemoval(RemovalNotification<State, Set<KeyAndSource>> notification) {
Preconditions.checkState(RemovalCause.COLLECTED.equals(notification.getCause()));
synchronized (lock) {
for (KeyAndSource keyAndSource : notification.getValue()) {
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 (source instanceof Class || source == SourceProvider.UNKNOWN_SOURCE) {
source = null;
}
Object convertedSource = Errors.convert(source);
backingMap.computeIfAbsent(key, k -> LinkedHashMultiset.create()).add(convertedSource);
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);
}
}
}