| /* |
| * Copyright 2019 The gRPC Authors |
| * |
| * 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 io.grpc.xds.internal.security; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import javax.annotation.CheckReturnValue; |
| import javax.annotation.concurrent.ThreadSafe; |
| |
| /** |
| * A map for managing reference-counted shared resources - typically providers. |
| * |
| * <p>A key (of generic type K) identifies a provider (of generic type V). The map also depends on a |
| * factory {@link ValueFactory} to create a new instance of V as needed. Values are ref-counted and |
| * closed by calling {@link Closeable#close()} when ref-count reaches zero. |
| * |
| * @param <K> Key type for the map |
| * @param <V> Value type for the map - it should be a {@link Closeable} |
| */ |
| @ThreadSafe |
| public final class ReferenceCountingMap<K, V extends Closeable> { |
| |
| private final Map<K, Instance<V>> instances = new HashMap<>(); |
| private final ValueFactory<K, V> valueFactory; |
| |
| public ReferenceCountingMap(ValueFactory<K, V> valueFactory) { |
| checkNotNull(valueFactory, "valueFactory"); |
| this.valueFactory = valueFactory; |
| } |
| |
| /** |
| * Gets an existing instance of a provider. If it doesn't exist, creates a new one |
| * using the provided {@link ValueFactory <K, V>} |
| */ |
| @CheckReturnValue |
| public V get(K key) { |
| checkNotNull(key, "key"); |
| return getInternal(key); |
| } |
| |
| /** |
| * Releases an instance of the given value. |
| * |
| * <p>The instance must have been obtained from {@link #get(Object)}. Otherwise will throw |
| * IllegalArgumentException. |
| * |
| * <p>Caller must not release a reference more than once. It's advised that you clear the |
| * reference to the instance with the null returned by this method. |
| * |
| * @param key for the instance to be released |
| * @param value the instance to be released |
| * @return a null which the caller can use to clear the reference to that instance. |
| */ |
| public V release(K key, V value) { |
| checkNotNull(key, "key"); |
| checkNotNull(value, "value"); |
| return releaseInternal(key, value); |
| } |
| |
| private synchronized V getInternal(K key) { |
| Instance<V> instance = instances.get(key); |
| if (instance == null) { |
| instance = new Instance<>(valueFactory.create(key)); |
| instances.put(key, instance); |
| return instance.value; |
| } else { |
| return instance.acquire(); |
| } |
| } |
| |
| private synchronized V releaseInternal(K key, V value) { |
| Instance<V> cached = instances.get(key); |
| checkArgument(cached != null, "No cached instance found for %s", key); |
| checkArgument(value == cached.value, "Releasing the wrong instance"); |
| if (cached.release()) { |
| try { |
| cached.value.close(); |
| } finally { |
| instances.remove(key); |
| } |
| } |
| // Always return null |
| return null; |
| } |
| |
| /** A factory to create a value from the given key. */ |
| public interface ValueFactory<K, V extends Closeable> { |
| V create(K key); |
| } |
| |
| private static final class Instance<V extends Closeable> { |
| final V value; |
| private int refCount; |
| |
| /** Increment refCount and acquire a reference to value. */ |
| V acquire() { |
| refCount++; |
| return value; |
| } |
| |
| /** Decrement refCount and return true if it has reached 0. */ |
| boolean release() { |
| checkState(refCount > 0, "refCount has to be > 0"); |
| return --refCount == 0; |
| } |
| |
| Instance(V value) { |
| this.value = value; |
| this.refCount = 1; |
| } |
| } |
| } |